From d0eff534d97766573a1900b49e95f29fda750a2b Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Mon, 9 Oct 2023 17:07:28 -0700 Subject: [PATCH] feat: Batch sequence steps in sequence creation and implement for Salesloft + Outeach (#1661) Fixes: https://linear.app/supaglue/issue/SUP-474/add-unified-api-endpoints-for-createupdate-sequence-sequence-steps-for ## Test Plan Manually hitting the implemented API endpoints and checking if the required objects have been created in Salesloft and outreach. Translate `intervalSeconds` to salesloft's day semantic. [Breaking] Default to 0 for `intervalSeconds` in outreach if `date` (for absolute-dated sequences) is also not specified) --- apps/api/package.json | 1 + apps/api/routes/oauth.ts | 41 +-- apps/salesforce-pub-sub/gen/pubsub_api_pb.ts | 232 +++++++---------- .../engagement/create-sequence-step.api.mdx | 6 +- .../api/v2/engagement/create-sequence.api.mdx | 16 +- .../components/schemas/create_sequence.yaml | 4 + .../schemas/create_sequence_step.yaml | 10 +- openapi/v2/engagement/openapi.bundle.json | 17 +- packages/core/remotes/impl/outreach/index.ts | 16 +- .../remotes/impl/outreach/mappers.test.ts | 4 +- .../core/remotes/impl/outreach/mappers.ts | 10 +- packages/core/remotes/impl/salesloft/index.ts | 31 +++ .../remotes/impl/salesloft/mappers.test.ts | 93 ++++++- .../core/remotes/impl/salesloft/mappers.ts | 236 ++++++++++++++++++ packages/schemas/gen/v2/engagement.ts | 13 +- packages/types/engagement/sequence.ts | 2 + packages/types/engagement/sequence_step.ts | 10 +- yarn.lock | 1 + 18 files changed, 561 insertions(+), 182 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index 43884ec10..0357ce79d 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -29,6 +29,7 @@ "uuid": "^9.0.0" }, "devDependencies": { + "@hapi/boom": "^10.0.1", "@tsconfig/node18": "^1.0.1", "@types/cors": "^2.8.13", "@types/express": "^4.17.17", diff --git a/apps/api/routes/oauth.ts b/apps/api/routes/oauth.ts index 5b9c17a3a..053030d2a 100644 --- a/apps/api/routes/oauth.ts +++ b/apps/api/routes/oauth.ts @@ -1,6 +1,7 @@ import { getDependencyContainer } from '@/dependency_container'; +import type { Boom } from '@hapi/boom'; import { Client as HubspotClient } from '@hubspot/api-client'; -import { BadRequestError } from '@supaglue/core/errors'; +import { BadRequestError, InternalServerError } from '@supaglue/core/errors'; import { getConnectorAuthConfig } from '@supaglue/core/remotes'; import type { ConnectionCreateParamsAny, @@ -25,6 +26,10 @@ import { Router } from 'express'; import type { AuthorizationMethod } from 'simple-oauth2'; import simpleOauth2 from 'simple-oauth2'; +function isBoom(err: any): err is Boom { + return err.isBoom; +} + const { providerService, connectionAndSyncService, applicationService } = getDependencyContainer(); const SERVER_URL = process.env.SUPAGLUE_SERVER_URL ?? 'http://localhost:8080'; @@ -231,21 +236,25 @@ export default function init(app: Router): void { // TODO: We should move all the logic that is conditional on providerName // to their respective files - const tokenWrapper = await client.getToken( - { - code, - redirect_uri: REDIRECT_URI, - ...additionalAuthParams, - }, - { - headers: - providerName === 'gong' - ? { - Authorization: `Basic ${Buffer.from(`${oauthClientId}:${oauthClientSecret}`).toString('base64')}`, - } - : undefined, - } - ); + const tokenWrapper = await client + .getToken( + { code, redirect_uri: REDIRECT_URI, ...additionalAuthParams }, + { + headers: + providerName === 'gong' + ? { Authorization: `Basic ${Buffer.from(`${oauthClientId}:${oauthClientSecret}`).toString('base64')}` } + : undefined, + } + ) + .catch((err) => { + if (isBoom(err)) { + // simple-oauth2 throws boom error. + // Avoids circular reference issue when throwing a boom error directly + // Though we aren't able to get the actual json response data @see https://share.cleanshot.com/DwwWn5Cj + throw new InternalServerError(err.message); + } + throw err; + }); let instanceUrl = (tokenWrapper.token['instance_url'] as string) ?? ''; diff --git a/apps/salesforce-pub-sub/gen/pubsub_api_pb.ts b/apps/salesforce-pub-sub/gen/pubsub_api_pb.ts index 177842c30..df91fa6c5 100644 --- a/apps/salesforce-pub-sub/gen/pubsub_api_pb.ts +++ b/apps/salesforce-pub-sub/gen/pubsub_api_pb.ts @@ -6,15 +6,8 @@ /* eslint-disable */ // @ts-nocheck -import type { - BinaryReadOptions, - FieldList, - JsonReadOptions, - JsonValue, - PartialMessage, - PlainMessage, -} from '@bufbuild/protobuf'; -import { Message, proto3 } from '@bufbuild/protobuf'; +import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; +import { Message, proto3 } from "@bufbuild/protobuf"; /** * Supported error codes @@ -33,9 +26,9 @@ export enum ErrorCode { PUBLISH = 1, } // Retrieve enum metadata with: proto3.getEnumType(ErrorCode) -proto3.util.setEnumType(ErrorCode, 'eventbus.v1.ErrorCode', [ - { no: 0, name: 'UNKNOWN' }, - { no: 1, name: 'PUBLISH' }, +proto3.util.setEnumType(ErrorCode, "eventbus.v1.ErrorCode", [ + { no: 0, name: "UNKNOWN" }, + { no: 1, name: "PUBLISH" }, ]); /** @@ -68,10 +61,10 @@ export enum ReplayPreset { CUSTOM = 2, } // Retrieve enum metadata with: proto3.getEnumType(ReplayPreset) -proto3.util.setEnumType(ReplayPreset, 'eventbus.v1.ReplayPreset', [ - { no: 0, name: 'LATEST' }, - { no: 1, name: 'EARLIEST' }, - { no: 2, name: 'CUSTOM' }, +proto3.util.setEnumType(ReplayPreset, "eventbus.v1.ReplayPreset", [ + { no: 0, name: "LATEST" }, + { no: 1, name: "EARLIEST" }, + { no: 2, name: "CUSTOM" }, ]); /** @@ -86,14 +79,14 @@ export class TopicInfo extends Message { * * @generated from field: string topic_name = 1; */ - topicName = ''; + topicName = ""; /** * Tenant/org GUID * * @generated from field: string tenant_guid = 2; */ - tenantGuid = ''; + tenantGuid = ""; /** * Is publishing allowed? @@ -115,14 +108,14 @@ export class TopicInfo extends Message { * * @generated from field: string schema_id = 5; */ - schemaId = ''; + schemaId = ""; /** * RPC ID used to trace errors. * * @generated from field: string rpc_id = 6; */ - rpcId = ''; + rpcId = ""; constructor(data?: PartialMessage) { super(); @@ -130,14 +123,14 @@ export class TopicInfo extends Message { } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = 'eventbus.v1.TopicInfo'; + static readonly typeName = "eventbus.v1.TopicInfo"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: 'topic_name', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 2, name: 'tenant_guid', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 3, name: 'can_publish', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, - { no: 4, name: 'can_subscribe', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, - { no: 5, name: 'schema_id', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 6, name: 'rpc_id', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 1, name: "topic_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "tenant_guid", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "can_publish", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + { no: 4, name: "can_subscribe", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + { no: 5, name: "schema_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 6, name: "rpc_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): TopicInfo { @@ -152,10 +145,7 @@ export class TopicInfo extends Message { return new TopicInfo().fromJsonString(jsonString, options); } - static equals( - a: TopicInfo | PlainMessage | undefined, - b: TopicInfo | PlainMessage | undefined - ): boolean { + static equals(a: TopicInfo | PlainMessage | undefined, b: TopicInfo | PlainMessage | undefined): boolean { return proto3.util.equals(TopicInfo, a, b); } } @@ -173,7 +163,7 @@ export class TopicRequest extends Message { * * @generated from field: string topic_name = 1; */ - topicName = ''; + topicName = ""; constructor(data?: PartialMessage) { super(); @@ -181,9 +171,9 @@ export class TopicRequest extends Message { } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = 'eventbus.v1.TopicRequest'; + static readonly typeName = "eventbus.v1.TopicRequest"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: 'topic_name', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 1, name: "topic_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): TopicRequest { @@ -198,10 +188,7 @@ export class TopicRequest extends Message { return new TopicRequest().fromJsonString(jsonString, options); } - static equals( - a: TopicRequest | PlainMessage | undefined, - b: TopicRequest | PlainMessage | undefined - ): boolean { + static equals(a: TopicRequest | PlainMessage | undefined, b: TopicRequest | PlainMessage | undefined): boolean { return proto3.util.equals(TopicRequest, a, b); } } @@ -219,7 +206,7 @@ export class EventHeader extends Message { /** * @generated from field: string key = 1; */ - key = ''; + key = ""; /** * @generated from field: bytes value = 2; @@ -232,10 +219,10 @@ export class EventHeader extends Message { } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = 'eventbus.v1.EventHeader'; + static readonly typeName = "eventbus.v1.EventHeader"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: 'key', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 2, name: 'value', kind: 'scalar', T: 12 /* ScalarType.BYTES */ }, + { no: 1, name: "key", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "value", kind: "scalar", T: 12 /* ScalarType.BYTES */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): EventHeader { @@ -250,10 +237,7 @@ export class EventHeader extends Message { return new EventHeader().fromJsonString(jsonString, options); } - static equals( - a: EventHeader | PlainMessage | undefined, - b: EventHeader | PlainMessage | undefined - ): boolean { + static equals(a: EventHeader | PlainMessage | undefined, b: EventHeader | PlainMessage | undefined): boolean { return proto3.util.equals(EventHeader, a, b); } } @@ -270,14 +254,14 @@ export class ProducerEvent extends Message { * * @generated from field: string id = 1; */ - id = ''; + id = ""; /** * Schema fingerprint for this event which is hash of the schema * * @generated from field: string schema_id = 2; */ - schemaId = ''; + schemaId = ""; /** * The message data field @@ -299,12 +283,12 @@ export class ProducerEvent extends Message { } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = 'eventbus.v1.ProducerEvent'; + static readonly typeName = "eventbus.v1.ProducerEvent"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: 'id', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 2, name: 'schema_id', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 3, name: 'payload', kind: 'scalar', T: 12 /* ScalarType.BYTES */ }, - { no: 4, name: 'headers', kind: 'message', T: EventHeader, repeated: true }, + { no: 1, name: "id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "schema_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "payload", kind: "scalar", T: 12 /* ScalarType.BYTES */ }, + { no: 4, name: "headers", kind: "message", T: EventHeader, repeated: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): ProducerEvent { @@ -319,10 +303,7 @@ export class ProducerEvent extends Message { return new ProducerEvent().fromJsonString(jsonString, options); } - static equals( - a: ProducerEvent | PlainMessage | undefined, - b: ProducerEvent | PlainMessage | undefined - ): boolean { + static equals(a: ProducerEvent | PlainMessage | undefined, b: ProducerEvent | PlainMessage | undefined): boolean { return proto3.util.equals(ProducerEvent, a, b); } } @@ -357,10 +338,10 @@ export class ConsumerEvent extends Message { } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = 'eventbus.v1.ConsumerEvent'; + static readonly typeName = "eventbus.v1.ConsumerEvent"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: 'event', kind: 'message', T: ProducerEvent }, - { no: 2, name: 'replay_id', kind: 'scalar', T: 12 /* ScalarType.BYTES */ }, + { no: 1, name: "event", kind: "message", T: ProducerEvent }, + { no: 2, name: "replay_id", kind: "scalar", T: 12 /* ScalarType.BYTES */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): ConsumerEvent { @@ -375,10 +356,7 @@ export class ConsumerEvent extends Message { return new ConsumerEvent().fromJsonString(jsonString, options); } - static equals( - a: ConsumerEvent | PlainMessage | undefined, - b: ConsumerEvent | PlainMessage | undefined - ): boolean { + static equals(a: ConsumerEvent | PlainMessage | undefined, b: ConsumerEvent | PlainMessage | undefined): boolean { return proto3.util.equals(ConsumerEvent, a, b); } } @@ -409,7 +387,7 @@ export class PublishResult extends Message { * * @generated from field: string correlationKey = 3; */ - correlationKey = ''; + correlationKey = ""; constructor(data?: PartialMessage) { super(); @@ -417,11 +395,11 @@ export class PublishResult extends Message { } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = 'eventbus.v1.PublishResult'; + static readonly typeName = "eventbus.v1.PublishResult"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: 'replay_id', kind: 'scalar', T: 12 /* ScalarType.BYTES */ }, - { no: 2, name: 'error', kind: 'message', T: Error }, - { no: 3, name: 'correlationKey', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 1, name: "replay_id", kind: "scalar", T: 12 /* ScalarType.BYTES */ }, + { no: 2, name: "error", kind: "message", T: Error }, + { no: 3, name: "correlationKey", kind: "scalar", T: 9 /* ScalarType.STRING */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): PublishResult { @@ -436,10 +414,7 @@ export class PublishResult extends Message { return new PublishResult().fromJsonString(jsonString, options); } - static equals( - a: PublishResult | PlainMessage | undefined, - b: PublishResult | PlainMessage | undefined - ): boolean { + static equals(a: PublishResult | PlainMessage | undefined, b: PublishResult | PlainMessage | undefined): boolean { return proto3.util.equals(PublishResult, a, b); } } @@ -462,7 +437,7 @@ export class Error extends Message { * * @generated from field: string msg = 2; */ - msg = ''; + msg = ""; constructor(data?: PartialMessage) { super(); @@ -470,10 +445,10 @@ export class Error extends Message { } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = 'eventbus.v1.Error'; + static readonly typeName = "eventbus.v1.Error"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: 'code', kind: 'enum', T: proto3.getEnumType(ErrorCode) }, - { no: 2, name: 'msg', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 1, name: "code", kind: "enum", T: proto3.getEnumType(ErrorCode) }, + { no: 2, name: "msg", kind: "scalar", T: 9 /* ScalarType.STRING */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Error { @@ -514,7 +489,7 @@ export class FetchRequest extends Message { * * @generated from field: string topic_name = 1; */ - topicName = ''; + topicName = ""; /** * @@ -551,7 +526,7 @@ export class FetchRequest extends Message { * * @generated from field: string auth_refresh = 5; */ - authRefresh = ''; + authRefresh = ""; constructor(data?: PartialMessage) { super(); @@ -559,13 +534,13 @@ export class FetchRequest extends Message { } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = 'eventbus.v1.FetchRequest'; + static readonly typeName = "eventbus.v1.FetchRequest"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: 'topic_name', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 2, name: 'replay_preset', kind: 'enum', T: proto3.getEnumType(ReplayPreset) }, - { no: 3, name: 'replay_id', kind: 'scalar', T: 12 /* ScalarType.BYTES */ }, - { no: 4, name: 'num_requested', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, - { no: 5, name: 'auth_refresh', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 1, name: "topic_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "replay_preset", kind: "enum", T: proto3.getEnumType(ReplayPreset) }, + { no: 3, name: "replay_id", kind: "scalar", T: 12 /* ScalarType.BYTES */ }, + { no: 4, name: "num_requested", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, + { no: 5, name: "auth_refresh", kind: "scalar", T: 9 /* ScalarType.STRING */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): FetchRequest { @@ -580,10 +555,7 @@ export class FetchRequest extends Message { return new FetchRequest().fromJsonString(jsonString, options); } - static equals( - a: FetchRequest | PlainMessage | undefined, - b: FetchRequest | PlainMessage | undefined - ): boolean { + static equals(a: FetchRequest | PlainMessage | undefined, b: FetchRequest | PlainMessage | undefined): boolean { return proto3.util.equals(FetchRequest, a, b); } } @@ -618,7 +590,7 @@ export class FetchResponse extends Message { * * @generated from field: string rpc_id = 3; */ - rpcId = ''; + rpcId = ""; /** * Number of remaining events to be delivered to the client for a Subscribe RPC call. @@ -633,12 +605,12 @@ export class FetchResponse extends Message { } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = 'eventbus.v1.FetchResponse'; + static readonly typeName = "eventbus.v1.FetchResponse"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: 'events', kind: 'message', T: ConsumerEvent, repeated: true }, - { no: 2, name: 'latest_replay_id', kind: 'scalar', T: 12 /* ScalarType.BYTES */ }, - { no: 3, name: 'rpc_id', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 4, name: 'pending_num_requested', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 1, name: "events", kind: "message", T: ConsumerEvent, repeated: true }, + { no: 2, name: "latest_replay_id", kind: "scalar", T: 12 /* ScalarType.BYTES */ }, + { no: 3, name: "rpc_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 4, name: "pending_num_requested", kind: "scalar", T: 5 /* ScalarType.INT32 */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): FetchResponse { @@ -653,10 +625,7 @@ export class FetchResponse extends Message { return new FetchResponse().fromJsonString(jsonString, options); } - static equals( - a: FetchResponse | PlainMessage | undefined, - b: FetchResponse | PlainMessage | undefined - ): boolean { + static equals(a: FetchResponse | PlainMessage | undefined, b: FetchResponse | PlainMessage | undefined): boolean { return proto3.util.equals(FetchResponse, a, b); } } @@ -673,7 +642,7 @@ export class SchemaRequest extends Message { * * @generated from field: string schema_id = 1; */ - schemaId = ''; + schemaId = ""; constructor(data?: PartialMessage) { super(); @@ -681,9 +650,9 @@ export class SchemaRequest extends Message { } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = 'eventbus.v1.SchemaRequest'; + static readonly typeName = "eventbus.v1.SchemaRequest"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: 'schema_id', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 1, name: "schema_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): SchemaRequest { @@ -698,10 +667,7 @@ export class SchemaRequest extends Message { return new SchemaRequest().fromJsonString(jsonString, options); } - static equals( - a: SchemaRequest | PlainMessage | undefined, - b: SchemaRequest | PlainMessage | undefined - ): boolean { + static equals(a: SchemaRequest | PlainMessage | undefined, b: SchemaRequest | PlainMessage | undefined): boolean { return proto3.util.equals(SchemaRequest, a, b); } } @@ -718,21 +684,21 @@ export class SchemaInfo extends Message { * * @generated from field: string schema_json = 1; */ - schemaJson = ''; + schemaJson = ""; /** * Schema fingerprint * * @generated from field: string schema_id = 2; */ - schemaId = ''; + schemaId = ""; /** * RPC ID used to trace errors. * * @generated from field: string rpc_id = 3; */ - rpcId = ''; + rpcId = ""; constructor(data?: PartialMessage) { super(); @@ -740,11 +706,11 @@ export class SchemaInfo extends Message { } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = 'eventbus.v1.SchemaInfo'; + static readonly typeName = "eventbus.v1.SchemaInfo"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: 'schema_json', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 2, name: 'schema_id', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 3, name: 'rpc_id', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 1, name: "schema_json", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "schema_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "rpc_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): SchemaInfo { @@ -759,10 +725,7 @@ export class SchemaInfo extends Message { return new SchemaInfo().fromJsonString(jsonString, options); } - static equals( - a: SchemaInfo | PlainMessage | undefined, - b: SchemaInfo | PlainMessage | undefined - ): boolean { + static equals(a: SchemaInfo | PlainMessage | undefined, b: SchemaInfo | PlainMessage | undefined): boolean { return proto3.util.equals(SchemaInfo, a, b); } } @@ -778,7 +741,7 @@ export class PublishRequest extends Message { * * @generated from field: string topic_name = 1; */ - topicName = ''; + topicName = ""; /** * Batch of ProducerEvent(s) to send @@ -792,7 +755,7 @@ export class PublishRequest extends Message { * * @generated from field: string auth_refresh = 3; */ - authRefresh = ''; + authRefresh = ""; constructor(data?: PartialMessage) { super(); @@ -800,11 +763,11 @@ export class PublishRequest extends Message { } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = 'eventbus.v1.PublishRequest'; + static readonly typeName = "eventbus.v1.PublishRequest"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: 'topic_name', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 2, name: 'events', kind: 'message', T: ProducerEvent, repeated: true }, - { no: 3, name: 'auth_refresh', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 1, name: "topic_name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "events", kind: "message", T: ProducerEvent, repeated: true }, + { no: 3, name: "auth_refresh", kind: "scalar", T: 9 /* ScalarType.STRING */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): PublishRequest { @@ -819,10 +782,7 @@ export class PublishRequest extends Message { return new PublishRequest().fromJsonString(jsonString, options); } - static equals( - a: PublishRequest | PlainMessage | undefined, - b: PublishRequest | PlainMessage | undefined - ): boolean { + static equals(a: PublishRequest | PlainMessage | undefined, b: PublishRequest | PlainMessage | undefined): boolean { return proto3.util.equals(PublishRequest, a, b); } } @@ -850,14 +810,14 @@ export class PublishResponse extends Message { * * @generated from field: string schema_id = 2; */ - schemaId = ''; + schemaId = ""; /** * RPC ID used to trace errors. * * @generated from field: string rpc_id = 3; */ - rpcId = ''; + rpcId = ""; constructor(data?: PartialMessage) { super(); @@ -865,11 +825,11 @@ export class PublishResponse extends Message { } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = 'eventbus.v1.PublishResponse'; + static readonly typeName = "eventbus.v1.PublishResponse"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: 'results', kind: 'message', T: PublishResult, repeated: true }, - { no: 2, name: 'schema_id', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, - { no: 3, name: 'rpc_id', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 1, name: "results", kind: "message", T: PublishResult, repeated: true }, + { no: 2, name: "schema_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "rpc_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): PublishResponse { @@ -884,10 +844,8 @@ export class PublishResponse extends Message { return new PublishResponse().fromJsonString(jsonString, options); } - static equals( - a: PublishResponse | PlainMessage | undefined, - b: PublishResponse | PlainMessage | undefined - ): boolean { + static equals(a: PublishResponse | PlainMessage | undefined, b: PublishResponse | PlainMessage | undefined): boolean { return proto3.util.equals(PublishResponse, a, b); } } + diff --git a/docs/docs/api/v2/engagement/create-sequence-step.api.mdx b/docs/docs/api/v2/engagement/create-sequence-step.api.mdx index 70473f23d..00490b4f8 100644 --- a/docs/docs/api/v2/engagement/create-sequence-step.api.mdx +++ b/docs/docs/api/v2/engagement/create-sequence-step.api.mdx @@ -5,7 +5,7 @@ description: "Create sequence step" sidebar_label: "Create sequence step" hide_title: true hide_table_of_contents: true -api: {"operationId":"createSequenceStep","tags":["Sequences"],"security":[{"x-api-key":[]}],"parameters":[{"name":"x-customer-id","in":"header","schema":{"type":"string","example":"my-customer-1"},"description":"The customer ID that uniquely identifies the customer in your application","required":true},{"name":"x-provider-name","in":"header","schema":{"type":"string","example":"outreach"},"description":"The provider name","required":true},{"name":"sequence_id","in":"path","required":true,"description":"The ID of the sequence.","schema":{"type":"string","example":"0258cbc6-6020-430a-848e-aafacbadf4ae"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"record":{"type":"object","properties":{"interval_seconds":{"type":"number","description":"The interval (in seconds) until this step will activate; only applicable to interval-based sequences."},"date":{"type":"string","example":"2023-01-01","description":"The date this step will activate; only applicable to date-based sequences."},"template":{"description":"The email/message template to be used for this step. Only applicable for email or message steps.","oneOf":[{"type":"object","properties":{"id":{"type":"string","description":"The ID of the template to use for this step."}},"required":["id"]},{"type":"object","properties":{"body":{"type":"string","description":"The body of the email (HTML)."},"subject":{"type":"string","description":"The subject of the email."},"name":{"type":"string","description":"The name of the template."},"to":{"type":"array","description":"A list of default person and email address pairs to receive this template in the \"to\" field","items":{"type":"string"}},"cc":{"type":"array","description":"A list of default person and email address pairs to receive this template in the \"cc\" field","items":{"type":"string"}},"bcc":{"type":"array","description":"A list of default person and email address pairs to receive this template in the \"bcc\" field","items":{"type":"string"}},"custom_fields":{"type":"object","additionalProperties":true,"description":"Custom properties to be inserted that are not covered by the common object. Object keys must match exactly to the corresponding provider API.","title":"custom_fields"}},"required":["body","subject","name"]}]},"is_reply":{"type":"boolean","description":"If true, this step will be sent as a reply to the previous step."},"order":{"type":"number","description":"The step's display order within its sequence."},"type":{"type":"string","enum":["auto_email","manual_email","call","task","linkedin_send_message"],"description":"The type of the sequence state. Note: `linkedin_send_message` is undocumented in Outreach and subject to change.\n"},"task_note":{"type":"string","description":"An optional note to be attached to this step."},"custom_fields":{"type":"object","additionalProperties":true,"description":"Custom properties to be inserted that are not covered by the common object. Object keys must match exactly to the corresponding provider API.","title":"custom_fields"}},"required":["template","is_reply","order","type"],"title":"create_sequence_step"}},"required":["record"]}}}},"responses":{"201":{"description":"Sequence step created","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"detail":{"type":"string","description":"The full error message from the remote Provider. The schema and level of detail will vary by Provider.","example":"{\"code\":400,\"body\":{\"status\":\"error\",\"message\":\"Property values were not valid: [{\\\\\"isValid\\\\\":false,\\\\\"message\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\",\\\\\"error\\\\\":\\\\\"PROPERTY_DOESNT_EXIST\\\\\",\\\\\"name\\\\\":\\\\\"__about_us\\\\\",\\\\\"localizedErrorMessage\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\"}]\",\"correlationId\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"category\":\"VALIDATION_ERROR\"},\"headers\":{\"access-control-allow-credentials\":\"false\",\"cf-cache-status\":\"DYNAMIC\",\"cf-ray\":\"8053d17b9dae9664-SJC\",\"connection\":\"close\",\"content-length\":\"361\",\"content-type\":\"application/json;charset=utf-8\",\"date\":\"Mon, 11 Sep 2023 23:51:22 GMT\",\"nel\":\"{\\\\\"success_fraction\\\\\":0.01,\\\\\"report_to\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"report-to\":\"{\\\\\"endpoints\\\\\":[{\\\\\"url\\\\\":\\\\\"https://a.nel.cloudflare.com/report/v3?s=FgwuXObO%2Fz6ahUJKsxjDLaXTWjooJ8tB0w4%2B%2BKaulGStx0FGkn1PoJoOx2KrFMfihzNdfAqikq7CmgbdlmwKB8hkmp3eTb68qpg10LXFlRgiSqRhbWM7yYSfo8CXmPBc\\\\\"}],\\\\\"group\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"server\":\"cloudflare\",\"strict-transport-security\":\"max-age=31536000; includeSubDomains; preload\",\"vary\":\"origin, Accept-Encoding\",\"x-content-type-options\":\"nosniff\",\"x-envoy-upstream-service-time\":\"91\",\"x-evy-trace-listener\":\"listener_https\",\"x-evy-trace-route-configuration\":\"listener_https/all\",\"x-evy-trace-route-service-name\":\"envoyset-translator\",\"x-evy-trace-served-by-pod\":\"iad02/hubapi-td/envoy-proxy-6c94986c56-9xsh2\",\"x-evy-trace-virtual-host\":\"all\",\"x-hubspot-correlation-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-hubspot-ratelimit-interval-milliseconds\":\"10000\",\"x-hubspot-ratelimit-max\":\"100\",\"x-hubspot-ratelimit-remaining\":\"99\",\"x-hubspot-ratelimit-secondly\":\"10\",\"x-hubspot-ratelimit-secondly-remaining\":\"9\",\"x-request-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-trace\":\"2B1B4386362759B6A4C34802AD168B803DDC1BE770000000000000000000\"}}\n"},"problem_type":{"type":"string","description":"The Supaglue error code associated with the error.","example":"MISSING_REQUIRED_FIELD"},"title":{"type":"string","description":"A brief description of the error. The schema and type of message will vary by Provider.","example":"Property values were not valid\n"}},"example":[{"detail":"{\"code\":400,\"body\":{\"status\":\"error\",\"message\":\"Property values were not valid: [{\\\\\"isValid\\\\\":false,\\\\\"message\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\",\\\\\"error\\\\\":\\\\\"PROPERTY_DOESNT_EXIST\\\\\",\\\\\"name\\\\\":\\\\\"__about_us\\\\\",\\\\\"localizedErrorMessage\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\"}]\",\"correlationId\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"category\":\"VALIDATION_ERROR\"},\"headers\":{\"access-control-allow-credentials\":\"false\",\"cf-cache-status\":\"DYNAMIC\",\"cf-ray\":\"8053d17b9dae9664-SJC\",\"connection\":\"close\",\"content-length\":\"361\",\"content-type\":\"application/json;charset=utf-8\",\"date\":\"Mon, 11 Sep 2023 23:51:22 GMT\",\"nel\":\"{\\\\\"success_fraction\\\\\":0.01,\\\\\"report_to\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"report-to\":\"{\\\\\"endpoints\\\\\":[{\\\\\"url\\\\\":\\\\\"https://a.nel.cloudflare.com/report/v3?s=FgwuXObO%2Fz6ahUJKsxjDLaXTWjooJ8tB0w4%2B%2BKaulGStx0FGkn1PoJoOx2KrFMfihzNdfAqikq7CmgbdlmwKB8hkmp3eTb68qpg10LXFlRgiSqRhbWM7yYSfo8CXmPBc\\\\\"}],\\\\\"group\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"server\":\"cloudflare\",\"strict-transport-security\":\"max-age=31536000; includeSubDomains; preload\",\"vary\":\"origin, Accept-Encoding\",\"x-content-type-options\":\"nosniff\",\"x-envoy-upstream-service-time\":\"91\",\"x-evy-trace-listener\":\"listener_https\",\"x-evy-trace-route-configuration\":\"listener_https/all\",\"x-evy-trace-route-service-name\":\"envoyset-translator\",\"x-evy-trace-served-by-pod\":\"iad02/hubapi-td/envoy-proxy-6c94986c56-9xsh2\",\"x-evy-trace-virtual-host\":\"all\",\"x-hubspot-correlation-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-hubspot-ratelimit-interval-milliseconds\":\"10000\",\"x-hubspot-ratelimit-max\":\"100\",\"x-hubspot-ratelimit-remaining\":\"99\",\"x-hubspot-ratelimit-secondly\":\"10\",\"x-hubspot-ratelimit-secondly-remaining\":\"9\",\"x-request-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-trace\":\"2B1B4386362759B6A4C34802AD168B803DDC1BE770000000000000000000\"}}\n","problem_type":"BAD_REQUEST_ERROR","title":"Property values were not valid\n"}]},"title":"errors"},"record":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"title":"created_record"},"warnings":{"type":"array","items":{"type":"object","properties":{"detail":{"type":"string"},"problem_type":{"type":"string"},"title":{"type":"string"}}},"title":"warnings"}}}}}}},"description":"Create sequence step","method":"post","path":"/sequences/{sequence_id}/sequence_steps","servers":[{"url":"https://api.supaglue.io/engagement/v2","description":"Supaglue API"}],"securitySchemes":{"x-api-key":{"type":"apiKey","name":"x-api-key","in":"header","description":"API key to allow developers to access the API"}},"jsonRequestBodyExample":{"record":{"interval_seconds":0,"date":"2023-01-01","template":{"id":"string"},"is_reply":true,"order":0,"type":"auto_email","task_note":"string","custom_fields":{}}},"info":{"version":"0.16.8","title":"Unified Engagement API","contact":{"name":"Supaglue","email":"docs@supaglue.com","url":"https://supaglue.com"},"description":"#### Introduction\nWelcome to the Unified API (Engagement) documentation. You can use this API to write to multiple third-party providers within the Engagement category.\n\n[View common schema for Engagement](https://docs.supaglue.com/platform/common-schemas/engagement)\n\n[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/supaglue/workspace/supaglue-public/overview)\n\n#### Base API URL\n\n```\nhttps://api.supaglue.io/engagement/v2\n```\n"},"postman":{"name":"Create sequence step","description":{"type":"text/plain"},"url":{"path":["sequences",":sequence_id","sequence_steps"],"host":["{{baseUrl}}"],"query":[],"variable":[{"disabled":false,"description":{"content":"(Required) The ID of the sequence.","type":"text/plain"},"type":"any","value":"","key":"sequence_id"}]},"header":[{"disabled":false,"description":{"content":"(Required) The customer ID that uniquely identifies the customer in your application","type":"text/plain"},"key":"x-customer-id","value":""},{"disabled":false,"description":{"content":"(Required) The provider name","type":"text/plain"},"key":"x-provider-name","value":""},{"key":"Content-Type","value":"application/json"},{"key":"Accept","value":"application/json"}],"method":"POST","body":{"mode":"raw","raw":"\"\"","options":{"raw":{"language":"json"}}},"auth":{"type":"apikey","apikey":[{"type":"any","value":"x-api-key","key":"key"},{"type":"any","value":"","key":"value"},{"type":"any","value":"header","key":"in"}]}}} +api: {"operationId":"createSequenceStep","tags":["Sequences"],"security":[{"x-api-key":[]}],"parameters":[{"name":"x-customer-id","in":"header","schema":{"type":"string","example":"my-customer-1"},"description":"The customer ID that uniquely identifies the customer in your application","required":true},{"name":"x-provider-name","in":"header","schema":{"type":"string","example":"outreach"},"description":"The provider name","required":true},{"name":"sequence_id","in":"path","required":true,"description":"The ID of the sequence.","schema":{"type":"string","example":"0258cbc6-6020-430a-848e-aafacbadf4ae"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"record":{"type":"object","properties":{"name":{"type":"string","description":"The name given by the user for the step. Used by Salesloft only."},"interval_seconds":{"type":"number","description":"The interval (in seconds) until this step will activate after the previous step (in case of first step, relative to when prospect first enters a sequence); only applicable to interval-based sequences. This is 0 by default"},"date":{"type":"string","example":"2023-01-01","description":"The date this step will activate; only applicable to date-based sequences."},"template":{"description":"The email/message template to be used for this step. Only applicable for email or message steps.","oneOf":[{"type":"object","properties":{"id":{"type":"string","description":"The ID of the template to use for this step."}},"required":["id"]},{"type":"object","properties":{"body":{"type":"string","description":"The body of the email (HTML)."},"subject":{"type":"string","description":"The subject of the email."},"name":{"type":"string","description":"The name of the template."},"to":{"type":"array","description":"A list of default person and email address pairs to receive this template in the \"to\" field","items":{"type":"string"}},"cc":{"type":"array","description":"A list of default person and email address pairs to receive this template in the \"cc\" field","items":{"type":"string"}},"bcc":{"type":"array","description":"A list of default person and email address pairs to receive this template in the \"bcc\" field","items":{"type":"string"}},"custom_fields":{"type":"object","additionalProperties":true,"description":"Custom properties to be inserted that are not covered by the common object. Object keys must match exactly to the corresponding provider API.","title":"custom_fields"}},"required":["body","subject","name"]}]},"is_reply":{"type":"boolean","description":"If true, this step will be sent as a reply to the previous step."},"order":{"type":"number","description":"The step's display order within its sequence. Only applicable for Outreach when adding steps one at a time after the initial sequence creation, otherwise when creating steps together with sequence order is implicit based on the order of step within the step array. Salesloft does not use the `order` param, and order is instead determined by `interval_seconds` which translates into the `day` parameter"},"type":{"type":"string","enum":["auto_email","manual_email","call","task","linkedin_send_message"],"description":"The type of the sequence state. Note: `linkedin_send_message` is undocumented in Outreach and subject to change.\n"},"task_note":{"type":"string","description":"An optional note to be attached to this step."},"custom_fields":{"type":"object","additionalProperties":true,"description":"Custom properties to be inserted that are not covered by the common object. Object keys must match exactly to the corresponding provider API.","title":"custom_fields"}},"required":["type"],"title":"create_sequence_step"}},"required":["record"]}}}},"responses":{"201":{"description":"Sequence step created","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"detail":{"type":"string","description":"The full error message from the remote Provider. The schema and level of detail will vary by Provider.","example":"{\"code\":400,\"body\":{\"status\":\"error\",\"message\":\"Property values were not valid: [{\\\\\"isValid\\\\\":false,\\\\\"message\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\",\\\\\"error\\\\\":\\\\\"PROPERTY_DOESNT_EXIST\\\\\",\\\\\"name\\\\\":\\\\\"__about_us\\\\\",\\\\\"localizedErrorMessage\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\"}]\",\"correlationId\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"category\":\"VALIDATION_ERROR\"},\"headers\":{\"access-control-allow-credentials\":\"false\",\"cf-cache-status\":\"DYNAMIC\",\"cf-ray\":\"8053d17b9dae9664-SJC\",\"connection\":\"close\",\"content-length\":\"361\",\"content-type\":\"application/json;charset=utf-8\",\"date\":\"Mon, 11 Sep 2023 23:51:22 GMT\",\"nel\":\"{\\\\\"success_fraction\\\\\":0.01,\\\\\"report_to\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"report-to\":\"{\\\\\"endpoints\\\\\":[{\\\\\"url\\\\\":\\\\\"https://a.nel.cloudflare.com/report/v3?s=FgwuXObO%2Fz6ahUJKsxjDLaXTWjooJ8tB0w4%2B%2BKaulGStx0FGkn1PoJoOx2KrFMfihzNdfAqikq7CmgbdlmwKB8hkmp3eTb68qpg10LXFlRgiSqRhbWM7yYSfo8CXmPBc\\\\\"}],\\\\\"group\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"server\":\"cloudflare\",\"strict-transport-security\":\"max-age=31536000; includeSubDomains; preload\",\"vary\":\"origin, Accept-Encoding\",\"x-content-type-options\":\"nosniff\",\"x-envoy-upstream-service-time\":\"91\",\"x-evy-trace-listener\":\"listener_https\",\"x-evy-trace-route-configuration\":\"listener_https/all\",\"x-evy-trace-route-service-name\":\"envoyset-translator\",\"x-evy-trace-served-by-pod\":\"iad02/hubapi-td/envoy-proxy-6c94986c56-9xsh2\",\"x-evy-trace-virtual-host\":\"all\",\"x-hubspot-correlation-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-hubspot-ratelimit-interval-milliseconds\":\"10000\",\"x-hubspot-ratelimit-max\":\"100\",\"x-hubspot-ratelimit-remaining\":\"99\",\"x-hubspot-ratelimit-secondly\":\"10\",\"x-hubspot-ratelimit-secondly-remaining\":\"9\",\"x-request-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-trace\":\"2B1B4386362759B6A4C34802AD168B803DDC1BE770000000000000000000\"}}\n"},"problem_type":{"type":"string","description":"The Supaglue error code associated with the error.","example":"MISSING_REQUIRED_FIELD"},"title":{"type":"string","description":"A brief description of the error. The schema and type of message will vary by Provider.","example":"Property values were not valid\n"}},"example":[{"detail":"{\"code\":400,\"body\":{\"status\":\"error\",\"message\":\"Property values were not valid: [{\\\\\"isValid\\\\\":false,\\\\\"message\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\",\\\\\"error\\\\\":\\\\\"PROPERTY_DOESNT_EXIST\\\\\",\\\\\"name\\\\\":\\\\\"__about_us\\\\\",\\\\\"localizedErrorMessage\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\"}]\",\"correlationId\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"category\":\"VALIDATION_ERROR\"},\"headers\":{\"access-control-allow-credentials\":\"false\",\"cf-cache-status\":\"DYNAMIC\",\"cf-ray\":\"8053d17b9dae9664-SJC\",\"connection\":\"close\",\"content-length\":\"361\",\"content-type\":\"application/json;charset=utf-8\",\"date\":\"Mon, 11 Sep 2023 23:51:22 GMT\",\"nel\":\"{\\\\\"success_fraction\\\\\":0.01,\\\\\"report_to\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"report-to\":\"{\\\\\"endpoints\\\\\":[{\\\\\"url\\\\\":\\\\\"https://a.nel.cloudflare.com/report/v3?s=FgwuXObO%2Fz6ahUJKsxjDLaXTWjooJ8tB0w4%2B%2BKaulGStx0FGkn1PoJoOx2KrFMfihzNdfAqikq7CmgbdlmwKB8hkmp3eTb68qpg10LXFlRgiSqRhbWM7yYSfo8CXmPBc\\\\\"}],\\\\\"group\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"server\":\"cloudflare\",\"strict-transport-security\":\"max-age=31536000; includeSubDomains; preload\",\"vary\":\"origin, Accept-Encoding\",\"x-content-type-options\":\"nosniff\",\"x-envoy-upstream-service-time\":\"91\",\"x-evy-trace-listener\":\"listener_https\",\"x-evy-trace-route-configuration\":\"listener_https/all\",\"x-evy-trace-route-service-name\":\"envoyset-translator\",\"x-evy-trace-served-by-pod\":\"iad02/hubapi-td/envoy-proxy-6c94986c56-9xsh2\",\"x-evy-trace-virtual-host\":\"all\",\"x-hubspot-correlation-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-hubspot-ratelimit-interval-milliseconds\":\"10000\",\"x-hubspot-ratelimit-max\":\"100\",\"x-hubspot-ratelimit-remaining\":\"99\",\"x-hubspot-ratelimit-secondly\":\"10\",\"x-hubspot-ratelimit-secondly-remaining\":\"9\",\"x-request-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-trace\":\"2B1B4386362759B6A4C34802AD168B803DDC1BE770000000000000000000\"}}\n","problem_type":"BAD_REQUEST_ERROR","title":"Property values were not valid\n"}]},"title":"errors"},"record":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"title":"created_record"},"warnings":{"type":"array","items":{"type":"object","properties":{"detail":{"type":"string"},"problem_type":{"type":"string"},"title":{"type":"string"}}},"title":"warnings"}}}}}}},"description":"Create sequence step","method":"post","path":"/sequences/{sequence_id}/sequence_steps","servers":[{"url":"https://api.supaglue.io/engagement/v2","description":"Supaglue API"}],"securitySchemes":{"x-api-key":{"type":"apiKey","name":"x-api-key","in":"header","description":"API key to allow developers to access the API"}},"jsonRequestBodyExample":{"record":{"name":"string","interval_seconds":0,"date":"2023-01-01","template":{"id":"string"},"is_reply":true,"order":0,"type":"auto_email","task_note":"string","custom_fields":{}}},"info":{"version":"0.16.8","title":"Unified Engagement API","contact":{"name":"Supaglue","email":"docs@supaglue.com","url":"https://supaglue.com"},"description":"#### Introduction\nWelcome to the Unified API (Engagement) documentation. You can use this API to write to multiple third-party providers within the Engagement category.\n\n[View common schema for Engagement](https://docs.supaglue.com/platform/common-schemas/engagement)\n\n[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/supaglue/workspace/supaglue-public/overview)\n\n#### Base API URL\n\n```\nhttps://api.supaglue.io/engagement/v2\n```\n"},"postman":{"name":"Create sequence step","description":{"type":"text/plain"},"url":{"path":["sequences",":sequence_id","sequence_steps"],"host":["{{baseUrl}}"],"query":[],"variable":[{"disabled":false,"description":{"content":"(Required) The ID of the sequence.","type":"text/plain"},"type":"any","value":"","key":"sequence_id"}]},"header":[{"disabled":false,"description":{"content":"(Required) The customer ID that uniquely identifies the customer in your application","type":"text/plain"},"key":"x-customer-id","value":""},{"disabled":false,"description":{"content":"(Required) The provider name","type":"text/plain"},"key":"x-provider-name","value":""},{"key":"Content-Type","value":"application/json"},{"key":"Accept","value":"application/json"}],"method":"POST","body":{"mode":"raw","raw":"\"\"","options":{"raw":{"language":"json"}}},"auth":{"type":"apikey","apikey":[{"type":"any","value":"x-api-key","key":"key"},{"type":"any","value":"","key":"value"},{"type":"any","value":"header","key":"in"}]}}} sidebar_class_name: "post api-method" info_path: api/v2/engagement/unified-engagement-api custom_edit_url: null @@ -34,7 +34,7 @@ Create sequence step ## Request -

Path Parameters

Header Parameters

Body

required
    record objectrequired
    template objectrequired
    +

    Path Parameters

    Header Parameters

    Body

    required
      record objectrequired
      template object
      The email/message template to be used for this step. Only applicable for email or message steps. @@ -42,7 +42,7 @@ The email/message template to be used for this step. Only applicable for email o Custom properties to be inserted that are not covered by the common object. Object keys must match exactly to the corresponding provider API. -
    custom_fields object
    +
    custom_fields object
    Custom properties to be inserted that are not covered by the common object. Object keys must match exactly to the corresponding provider API. diff --git a/docs/docs/api/v2/engagement/create-sequence.api.mdx b/docs/docs/api/v2/engagement/create-sequence.api.mdx index b40c96eb6..3b6107ba5 100644 --- a/docs/docs/api/v2/engagement/create-sequence.api.mdx +++ b/docs/docs/api/v2/engagement/create-sequence.api.mdx @@ -5,7 +5,7 @@ description: "Create sequence" sidebar_label: "Create sequence" hide_title: true hide_table_of_contents: true -api: {"operationId":"createSequence","tags":["Sequences"],"security":[{"x-api-key":[]}],"parameters":[{"name":"x-customer-id","in":"header","schema":{"type":"string","example":"my-customer-1"},"description":"The customer ID that uniquely identifies the customer in your application","required":true},{"name":"x-provider-name","in":"header","schema":{"type":"string","example":"outreach"},"description":"The provider name","required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"record":{"type":"object","properties":{"name":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"type":{"type":"string","description":"The share type of the sequence. Setting to `team` will share with the whole team. `private` will only share with the owner.","enum":["team","private"]},"owner_id":{"type":"string"},"custom_fields":{"type":"object","additionalProperties":true,"description":"Custom properties to be inserted that are not covered by the common object. Object keys must match exactly to the corresponding provider API.","title":"custom_fields"}},"required":["name","type"],"title":"create_sequence"}},"required":["record"]}}}},"responses":{"201":{"description":"Sequence created","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"detail":{"type":"string","description":"The full error message from the remote Provider. The schema and level of detail will vary by Provider.","example":"{\"code\":400,\"body\":{\"status\":\"error\",\"message\":\"Property values were not valid: [{\\\\\"isValid\\\\\":false,\\\\\"message\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\",\\\\\"error\\\\\":\\\\\"PROPERTY_DOESNT_EXIST\\\\\",\\\\\"name\\\\\":\\\\\"__about_us\\\\\",\\\\\"localizedErrorMessage\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\"}]\",\"correlationId\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"category\":\"VALIDATION_ERROR\"},\"headers\":{\"access-control-allow-credentials\":\"false\",\"cf-cache-status\":\"DYNAMIC\",\"cf-ray\":\"8053d17b9dae9664-SJC\",\"connection\":\"close\",\"content-length\":\"361\",\"content-type\":\"application/json;charset=utf-8\",\"date\":\"Mon, 11 Sep 2023 23:51:22 GMT\",\"nel\":\"{\\\\\"success_fraction\\\\\":0.01,\\\\\"report_to\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"report-to\":\"{\\\\\"endpoints\\\\\":[{\\\\\"url\\\\\":\\\\\"https://a.nel.cloudflare.com/report/v3?s=FgwuXObO%2Fz6ahUJKsxjDLaXTWjooJ8tB0w4%2B%2BKaulGStx0FGkn1PoJoOx2KrFMfihzNdfAqikq7CmgbdlmwKB8hkmp3eTb68qpg10LXFlRgiSqRhbWM7yYSfo8CXmPBc\\\\\"}],\\\\\"group\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"server\":\"cloudflare\",\"strict-transport-security\":\"max-age=31536000; includeSubDomains; preload\",\"vary\":\"origin, Accept-Encoding\",\"x-content-type-options\":\"nosniff\",\"x-envoy-upstream-service-time\":\"91\",\"x-evy-trace-listener\":\"listener_https\",\"x-evy-trace-route-configuration\":\"listener_https/all\",\"x-evy-trace-route-service-name\":\"envoyset-translator\",\"x-evy-trace-served-by-pod\":\"iad02/hubapi-td/envoy-proxy-6c94986c56-9xsh2\",\"x-evy-trace-virtual-host\":\"all\",\"x-hubspot-correlation-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-hubspot-ratelimit-interval-milliseconds\":\"10000\",\"x-hubspot-ratelimit-max\":\"100\",\"x-hubspot-ratelimit-remaining\":\"99\",\"x-hubspot-ratelimit-secondly\":\"10\",\"x-hubspot-ratelimit-secondly-remaining\":\"9\",\"x-request-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-trace\":\"2B1B4386362759B6A4C34802AD168B803DDC1BE770000000000000000000\"}}\n"},"problem_type":{"type":"string","description":"The Supaglue error code associated with the error.","example":"MISSING_REQUIRED_FIELD"},"title":{"type":"string","description":"A brief description of the error. The schema and type of message will vary by Provider.","example":"Property values were not valid\n"}},"example":[{"detail":"{\"code\":400,\"body\":{\"status\":\"error\",\"message\":\"Property values were not valid: [{\\\\\"isValid\\\\\":false,\\\\\"message\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\",\\\\\"error\\\\\":\\\\\"PROPERTY_DOESNT_EXIST\\\\\",\\\\\"name\\\\\":\\\\\"__about_us\\\\\",\\\\\"localizedErrorMessage\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\"}]\",\"correlationId\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"category\":\"VALIDATION_ERROR\"},\"headers\":{\"access-control-allow-credentials\":\"false\",\"cf-cache-status\":\"DYNAMIC\",\"cf-ray\":\"8053d17b9dae9664-SJC\",\"connection\":\"close\",\"content-length\":\"361\",\"content-type\":\"application/json;charset=utf-8\",\"date\":\"Mon, 11 Sep 2023 23:51:22 GMT\",\"nel\":\"{\\\\\"success_fraction\\\\\":0.01,\\\\\"report_to\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"report-to\":\"{\\\\\"endpoints\\\\\":[{\\\\\"url\\\\\":\\\\\"https://a.nel.cloudflare.com/report/v3?s=FgwuXObO%2Fz6ahUJKsxjDLaXTWjooJ8tB0w4%2B%2BKaulGStx0FGkn1PoJoOx2KrFMfihzNdfAqikq7CmgbdlmwKB8hkmp3eTb68qpg10LXFlRgiSqRhbWM7yYSfo8CXmPBc\\\\\"}],\\\\\"group\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"server\":\"cloudflare\",\"strict-transport-security\":\"max-age=31536000; includeSubDomains; preload\",\"vary\":\"origin, Accept-Encoding\",\"x-content-type-options\":\"nosniff\",\"x-envoy-upstream-service-time\":\"91\",\"x-evy-trace-listener\":\"listener_https\",\"x-evy-trace-route-configuration\":\"listener_https/all\",\"x-evy-trace-route-service-name\":\"envoyset-translator\",\"x-evy-trace-served-by-pod\":\"iad02/hubapi-td/envoy-proxy-6c94986c56-9xsh2\",\"x-evy-trace-virtual-host\":\"all\",\"x-hubspot-correlation-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-hubspot-ratelimit-interval-milliseconds\":\"10000\",\"x-hubspot-ratelimit-max\":\"100\",\"x-hubspot-ratelimit-remaining\":\"99\",\"x-hubspot-ratelimit-secondly\":\"10\",\"x-hubspot-ratelimit-secondly-remaining\":\"9\",\"x-request-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-trace\":\"2B1B4386362759B6A4C34802AD168B803DDC1BE770000000000000000000\"}}\n","problem_type":"BAD_REQUEST_ERROR","title":"Property values were not valid\n"}]},"title":"errors"},"record":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"title":"created_record"},"warnings":{"type":"array","items":{"type":"object","properties":{"detail":{"type":"string"},"problem_type":{"type":"string"},"title":{"type":"string"}}},"title":"warnings"}}}}}}},"description":"Create sequence","method":"post","path":"/sequences","servers":[{"url":"https://api.supaglue.io/engagement/v2","description":"Supaglue API"}],"securitySchemes":{"x-api-key":{"type":"apiKey","name":"x-api-key","in":"header","description":"API key to allow developers to access the API"}},"jsonRequestBodyExample":{"record":{"name":"string","tags":["string"],"type":"team","owner_id":"string","custom_fields":{}}},"info":{"version":"0.16.8","title":"Unified Engagement API","contact":{"name":"Supaglue","email":"docs@supaglue.com","url":"https://supaglue.com"},"description":"#### Introduction\nWelcome to the Unified API (Engagement) documentation. You can use this API to write to multiple third-party providers within the Engagement category.\n\n[View common schema for Engagement](https://docs.supaglue.com/platform/common-schemas/engagement)\n\n[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/supaglue/workspace/supaglue-public/overview)\n\n#### Base API URL\n\n```\nhttps://api.supaglue.io/engagement/v2\n```\n"},"postman":{"name":"Create sequence","description":{"type":"text/plain"},"url":{"path":["sequences"],"host":["{{baseUrl}}"],"query":[],"variable":[]},"header":[{"disabled":false,"description":{"content":"(Required) The customer ID that uniquely identifies the customer in your application","type":"text/plain"},"key":"x-customer-id","value":""},{"disabled":false,"description":{"content":"(Required) The provider name","type":"text/plain"},"key":"x-provider-name","value":""},{"key":"Content-Type","value":"application/json"},{"key":"Accept","value":"application/json"}],"method":"POST","body":{"mode":"raw","raw":"\"\"","options":{"raw":{"language":"json"}}},"auth":{"type":"apikey","apikey":[{"type":"any","value":"x-api-key","key":"key"},{"type":"any","value":"","key":"value"},{"type":"any","value":"header","key":"in"}]}}} +api: {"operationId":"createSequence","tags":["Sequences"],"security":[{"x-api-key":[]}],"parameters":[{"name":"x-customer-id","in":"header","schema":{"type":"string","example":"my-customer-1"},"description":"The customer ID that uniquely identifies the customer in your application","required":true},{"name":"x-provider-name","in":"header","schema":{"type":"string","example":"outreach"},"description":"The provider name","required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"record":{"type":"object","properties":{"name":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"type":{"type":"string","description":"The share type of the sequence. Setting to `team` will share with the whole team. `private` will only share with the owner.","enum":["team","private"]},"owner_id":{"type":"string"},"steps":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The name given by the user for the step. Used by Salesloft only."},"interval_seconds":{"type":"number","description":"The interval (in seconds) until this step will activate after the previous step (in case of first step, relative to when prospect first enters a sequence); only applicable to interval-based sequences. This is 0 by default"},"date":{"type":"string","example":"2023-01-01","description":"The date this step will activate; only applicable to date-based sequences."},"template":{"description":"The email/message template to be used for this step. Only applicable for email or message steps.","oneOf":[{"type":"object","properties":{"id":{"type":"string","description":"The ID of the template to use for this step."}},"required":["id"]},{"type":"object","properties":{"body":{"type":"string","description":"The body of the email (HTML)."},"subject":{"type":"string","description":"The subject of the email."},"name":{"type":"string","description":"The name of the template."},"to":{"type":"array","description":"A list of default person and email address pairs to receive this template in the \"to\" field","items":{"type":"string"}},"cc":{"type":"array","description":"A list of default person and email address pairs to receive this template in the \"cc\" field","items":{"type":"string"}},"bcc":{"type":"array","description":"A list of default person and email address pairs to receive this template in the \"bcc\" field","items":{"type":"string"}},"custom_fields":{"type":"object","additionalProperties":true,"description":"Custom properties to be inserted that are not covered by the common object. Object keys must match exactly to the corresponding provider API.","title":"custom_fields"}},"required":["body","subject","name"]}]},"is_reply":{"type":"boolean","description":"If true, this step will be sent as a reply to the previous step."},"order":{"type":"number","description":"The step's display order within its sequence. Only applicable for Outreach when adding steps one at a time after the initial sequence creation, otherwise when creating steps together with sequence order is implicit based on the order of step within the step array. Salesloft does not use the `order` param, and order is instead determined by `interval_seconds` which translates into the `day` parameter"},"type":{"type":"string","enum":["auto_email","manual_email","call","task","linkedin_send_message"],"description":"The type of the sequence state. Note: `linkedin_send_message` is undocumented in Outreach and subject to change.\n"},"task_note":{"type":"string","description":"An optional note to be attached to this step."},"custom_fields":{"type":"object","additionalProperties":true,"description":"Custom properties to be inserted that are not covered by the common object. Object keys must match exactly to the corresponding provider API.","title":"custom_fields"}},"required":["type"],"title":"create_sequence_step"}},"custom_fields":{"type":"object","additionalProperties":true,"description":"Custom properties to be inserted that are not covered by the common object. Object keys must match exactly to the corresponding provider API.","title":"custom_fields"}},"required":["name","type"],"title":"create_sequence"}},"required":["record"]}}}},"responses":{"201":{"description":"Sequence created","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"array","items":{"type":"object","properties":{"detail":{"type":"string","description":"The full error message from the remote Provider. The schema and level of detail will vary by Provider.","example":"{\"code\":400,\"body\":{\"status\":\"error\",\"message\":\"Property values were not valid: [{\\\\\"isValid\\\\\":false,\\\\\"message\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\",\\\\\"error\\\\\":\\\\\"PROPERTY_DOESNT_EXIST\\\\\",\\\\\"name\\\\\":\\\\\"__about_us\\\\\",\\\\\"localizedErrorMessage\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\"}]\",\"correlationId\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"category\":\"VALIDATION_ERROR\"},\"headers\":{\"access-control-allow-credentials\":\"false\",\"cf-cache-status\":\"DYNAMIC\",\"cf-ray\":\"8053d17b9dae9664-SJC\",\"connection\":\"close\",\"content-length\":\"361\",\"content-type\":\"application/json;charset=utf-8\",\"date\":\"Mon, 11 Sep 2023 23:51:22 GMT\",\"nel\":\"{\\\\\"success_fraction\\\\\":0.01,\\\\\"report_to\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"report-to\":\"{\\\\\"endpoints\\\\\":[{\\\\\"url\\\\\":\\\\\"https://a.nel.cloudflare.com/report/v3?s=FgwuXObO%2Fz6ahUJKsxjDLaXTWjooJ8tB0w4%2B%2BKaulGStx0FGkn1PoJoOx2KrFMfihzNdfAqikq7CmgbdlmwKB8hkmp3eTb68qpg10LXFlRgiSqRhbWM7yYSfo8CXmPBc\\\\\"}],\\\\\"group\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"server\":\"cloudflare\",\"strict-transport-security\":\"max-age=31536000; includeSubDomains; preload\",\"vary\":\"origin, Accept-Encoding\",\"x-content-type-options\":\"nosniff\",\"x-envoy-upstream-service-time\":\"91\",\"x-evy-trace-listener\":\"listener_https\",\"x-evy-trace-route-configuration\":\"listener_https/all\",\"x-evy-trace-route-service-name\":\"envoyset-translator\",\"x-evy-trace-served-by-pod\":\"iad02/hubapi-td/envoy-proxy-6c94986c56-9xsh2\",\"x-evy-trace-virtual-host\":\"all\",\"x-hubspot-correlation-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-hubspot-ratelimit-interval-milliseconds\":\"10000\",\"x-hubspot-ratelimit-max\":\"100\",\"x-hubspot-ratelimit-remaining\":\"99\",\"x-hubspot-ratelimit-secondly\":\"10\",\"x-hubspot-ratelimit-secondly-remaining\":\"9\",\"x-request-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-trace\":\"2B1B4386362759B6A4C34802AD168B803DDC1BE770000000000000000000\"}}\n"},"problem_type":{"type":"string","description":"The Supaglue error code associated with the error.","example":"MISSING_REQUIRED_FIELD"},"title":{"type":"string","description":"A brief description of the error. The schema and type of message will vary by Provider.","example":"Property values were not valid\n"}},"example":[{"detail":"{\"code\":400,\"body\":{\"status\":\"error\",\"message\":\"Property values were not valid: [{\\\\\"isValid\\\\\":false,\\\\\"message\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\",\\\\\"error\\\\\":\\\\\"PROPERTY_DOESNT_EXIST\\\\\",\\\\\"name\\\\\":\\\\\"__about_us\\\\\",\\\\\"localizedErrorMessage\\\\\":\\\\\"Property \\\\\\\\\\\\\"__about_us\\\\\\\\\\\\\" does not exist\\\\\"}]\",\"correlationId\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"category\":\"VALIDATION_ERROR\"},\"headers\":{\"access-control-allow-credentials\":\"false\",\"cf-cache-status\":\"DYNAMIC\",\"cf-ray\":\"8053d17b9dae9664-SJC\",\"connection\":\"close\",\"content-length\":\"361\",\"content-type\":\"application/json;charset=utf-8\",\"date\":\"Mon, 11 Sep 2023 23:51:22 GMT\",\"nel\":\"{\\\\\"success_fraction\\\\\":0.01,\\\\\"report_to\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"report-to\":\"{\\\\\"endpoints\\\\\":[{\\\\\"url\\\\\":\\\\\"https://a.nel.cloudflare.com/report/v3?s=FgwuXObO%2Fz6ahUJKsxjDLaXTWjooJ8tB0w4%2B%2BKaulGStx0FGkn1PoJoOx2KrFMfihzNdfAqikq7CmgbdlmwKB8hkmp3eTb68qpg10LXFlRgiSqRhbWM7yYSfo8CXmPBc\\\\\"}],\\\\\"group\\\\\":\\\\\"cf-nel\\\\\",\\\\\"max_age\\\\\":604800}\",\"server\":\"cloudflare\",\"strict-transport-security\":\"max-age=31536000; includeSubDomains; preload\",\"vary\":\"origin, Accept-Encoding\",\"x-content-type-options\":\"nosniff\",\"x-envoy-upstream-service-time\":\"91\",\"x-evy-trace-listener\":\"listener_https\",\"x-evy-trace-route-configuration\":\"listener_https/all\",\"x-evy-trace-route-service-name\":\"envoyset-translator\",\"x-evy-trace-served-by-pod\":\"iad02/hubapi-td/envoy-proxy-6c94986c56-9xsh2\",\"x-evy-trace-virtual-host\":\"all\",\"x-hubspot-correlation-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-hubspot-ratelimit-interval-milliseconds\":\"10000\",\"x-hubspot-ratelimit-max\":\"100\",\"x-hubspot-ratelimit-remaining\":\"99\",\"x-hubspot-ratelimit-secondly\":\"10\",\"x-hubspot-ratelimit-secondly-remaining\":\"9\",\"x-request-id\":\"ac94252c-90b5-45d2-ad1d-9a9f7651d7d2\",\"x-trace\":\"2B1B4386362759B6A4C34802AD168B803DDC1BE770000000000000000000\"}}\n","problem_type":"BAD_REQUEST_ERROR","title":"Property values were not valid\n"}]},"title":"errors"},"record":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"title":"created_record"},"warnings":{"type":"array","items":{"type":"object","properties":{"detail":{"type":"string"},"problem_type":{"type":"string"},"title":{"type":"string"}}},"title":"warnings"}}}}}}},"description":"Create sequence","method":"post","path":"/sequences","servers":[{"url":"https://api.supaglue.io/engagement/v2","description":"Supaglue API"}],"securitySchemes":{"x-api-key":{"type":"apiKey","name":"x-api-key","in":"header","description":"API key to allow developers to access the API"}},"jsonRequestBodyExample":{"record":{"name":"string","tags":["string"],"type":"team","owner_id":"string","steps":[{"name":"string","interval_seconds":0,"date":"2023-01-01","template":{"id":"string"},"is_reply":true,"order":0,"type":"auto_email","task_note":"string","custom_fields":{}}],"custom_fields":{}}},"info":{"version":"0.16.8","title":"Unified Engagement API","contact":{"name":"Supaglue","email":"docs@supaglue.com","url":"https://supaglue.com"},"description":"#### Introduction\nWelcome to the Unified API (Engagement) documentation. You can use this API to write to multiple third-party providers within the Engagement category.\n\n[View common schema for Engagement](https://docs.supaglue.com/platform/common-schemas/engagement)\n\n[![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/supaglue/workspace/supaglue-public/overview)\n\n#### Base API URL\n\n```\nhttps://api.supaglue.io/engagement/v2\n```\n"},"postman":{"name":"Create sequence","description":{"type":"text/plain"},"url":{"path":["sequences"],"host":["{{baseUrl}}"],"query":[],"variable":[]},"header":[{"disabled":false,"description":{"content":"(Required) The customer ID that uniquely identifies the customer in your application","type":"text/plain"},"key":"x-customer-id","value":""},{"disabled":false,"description":{"content":"(Required) The provider name","type":"text/plain"},"key":"x-provider-name","value":""},{"key":"Content-Type","value":"application/json"},{"key":"Accept","value":"application/json"}],"method":"POST","body":{"mode":"raw","raw":"\"\"","options":{"raw":{"language":"json"}}},"auth":{"type":"apikey","apikey":[{"type":"any","value":"x-api-key","key":"key"},{"type":"any","value":"","key":"value"},{"type":"any","value":"header","key":"in"}]}}} sidebar_class_name: "post api-method" info_path: api/v2/engagement/unified-engagement-api custom_edit_url: null @@ -34,7 +34,19 @@ Create sequence ## Request -

    Header Parameters

    Body

    required
      record objectrequired
      custom_fields object
      +

      Header Parameters

      Body

      required
        record objectrequired
        steps object[]
      • Array [
      • template object
        + +The email/message template to be used for this step. Only applicable for email or message steps. + +
        oneOf
        custom_fields object
        + +Custom properties to be inserted that are not covered by the common object. Object keys must match exactly to the corresponding provider API. + +
        custom_fields object
        + +Custom properties to be inserted that are not covered by the common object. Object keys must match exactly to the corresponding provider API. + +
      • ]
      • custom_fields object
        Custom properties to be inserted that are not covered by the common object. Object keys must match exactly to the corresponding provider API. diff --git a/openapi/v2/engagement/components/schemas/create_sequence.yaml b/openapi/v2/engagement/components/schemas/create_sequence.yaml index 44ee59cde..b00d56614 100644 --- a/openapi/v2/engagement/components/schemas/create_sequence.yaml +++ b/openapi/v2/engagement/components/schemas/create_sequence.yaml @@ -12,6 +12,10 @@ properties: enum: [team, private] owner_id: type: string + steps: + type: array + items: + $ref: ./create_sequence_step.yaml custom_fields: $ref: ./objects/custom_fields.yaml required: diff --git a/openapi/v2/engagement/components/schemas/create_sequence_step.yaml b/openapi/v2/engagement/components/schemas/create_sequence_step.yaml index eda9c1528..fed625f3c 100644 --- a/openapi/v2/engagement/components/schemas/create_sequence_step.yaml +++ b/openapi/v2/engagement/components/schemas/create_sequence_step.yaml @@ -1,8 +1,11 @@ type: object properties: + name: + type: string + description: The name given by the user for the step. Used by Salesloft only. interval_seconds: type: number - description: The interval (in seconds) until this step will activate; only applicable to interval-based sequences. + description: The interval (in seconds) until this step will activate after the previous step (in case of first step, relative to when prospect first enters a sequence); only applicable to interval-based sequences. This is 0 by default date: type: string example: "2023-01-01" @@ -54,7 +57,7 @@ properties: description: If true, this step will be sent as a reply to the previous step. order: type: number - description: The step's display order within its sequence. + description: The step's display order within its sequence. Only applicable for Outreach when adding steps one at a time after the initial sequence creation, otherwise when creating steps together with sequence order is implicit based on the order of step within the step array. Salesloft does not use the `order` param, and order is instead determined by `interval_seconds` which translates into the `day` parameter type: type: string enum: [auto_email, manual_email, call, task, linkedin_send_message] @@ -66,7 +69,4 @@ properties: custom_fields: $ref: ./objects/custom_fields.yaml required: - - template - - is_reply - - order - type diff --git a/openapi/v2/engagement/openapi.bundle.json b/openapi/v2/engagement/openapi.bundle.json index 54569cee3..e2f0b9811 100644 --- a/openapi/v2/engagement/openapi.bundle.json +++ b/openapi/v2/engagement/openapi.bundle.json @@ -1536,6 +1536,12 @@ "owner_id": { "type": "string" }, + "steps": { + "type": "array", + "items": { + "$ref": "#/components/schemas/create_sequence_step" + } + }, "custom_fields": { "$ref": "#/components/schemas/custom_fields" } @@ -1548,9 +1554,13 @@ "create_sequence_step": { "type": "object", "properties": { + "name": { + "type": "string", + "description": "The name given by the user for the step. Used by Salesloft only." + }, "interval_seconds": { "type": "number", - "description": "The interval (in seconds) until this step will activate; only applicable to interval-based sequences." + "description": "The interval (in seconds) until this step will activate after the previous step (in case of first step, relative to when prospect first enters a sequence); only applicable to interval-based sequences. This is 0 by default" }, "date": { "type": "string", @@ -1626,7 +1636,7 @@ }, "order": { "type": "number", - "description": "The step's display order within its sequence." + "description": "The step's display order within its sequence. Only applicable for Outreach when adding steps one at a time after the initial sequence creation, otherwise when creating steps together with sequence order is implicit based on the order of step within the step array. Salesloft does not use the `order` param, and order is instead determined by `interval_seconds` which translates into the `day` parameter" }, "type": { "type": "string", @@ -1648,9 +1658,6 @@ } }, "required": [ - "template", - "is_reply", - "order", "type" ] }, diff --git a/packages/core/remotes/impl/outreach/index.ts b/packages/core/remotes/impl/outreach/index.ts index f57da1407..ac3a7d03c 100644 --- a/packages/core/remotes/impl/outreach/index.ts +++ b/packages/core/remotes/impl/outreach/index.ts @@ -1478,7 +1478,21 @@ class OutreachClient extends AbstractEngagementRemoteClient { headers: this.getAuthHeadersForPassthroughRequest(), } ); - return response.data.data.id.toString(); + + const sequenceId = response.data.data.id.toString(); + + // There should be a low number of steps, so we will try to create them all in parallel. + await Promise.all( + (params.steps ?? []).map((step, index) => + this.createSequenceStep({ + ...step, + order: index + 1, // Ignore step.order and use the implicit order from array index instead. + sequenceId, // Ignore sequence.id as well. + }) + ) + ); + + return sequenceId; } async createSequenceStep(params: SequenceStepCreateParams): Promise { diff --git a/packages/core/remotes/impl/outreach/mappers.test.ts b/packages/core/remotes/impl/outreach/mappers.test.ts index 3c5059c6a..921defdc3 100644 --- a/packages/core/remotes/impl/outreach/mappers.test.ts +++ b/packages/core/remotes/impl/outreach/mappers.test.ts @@ -865,7 +865,7 @@ describe('Outreach mappers', () => { stepType: 'auto_email', order: 1, date: undefined, - interval: undefined, + interval: 0, taskNote: 'Test Note', custom1: 'value1', }, @@ -905,7 +905,7 @@ describe('Outreach mappers', () => { stepType: 'manual_email', order: 2, date: undefined, - interval: undefined, + interval: 0, custom3: 'value3', }, relationships: { diff --git a/packages/core/remotes/impl/outreach/mappers.ts b/packages/core/remotes/impl/outreach/mappers.ts index a4dd63e5f..24c941721 100644 --- a/packages/core/remotes/impl/outreach/mappers.ts +++ b/packages/core/remotes/impl/outreach/mappers.ts @@ -369,6 +369,14 @@ export const toOutreachSequenceStepCreateParams = ({ taskNote, customFields, }: SequenceStepCreateParams): Record => { + if (!sequenceId) { + throw new BadRequestError('Sequence ID is required for Outreach step creation'); + } + if (!date && !intervalSeconds) { + // outreach defaults to creating an initial step with a 1 week interval otherwise. + // This allows us to unify the semantics + intervalSeconds = 0; + } return { data: { attributes: { @@ -376,7 +384,7 @@ export const toOutreachSequenceStepCreateParams = ({ date, stepType: type, taskNote, - order, + order: order ?? 0, ...customFields, }, relationships: { diff --git a/packages/core/remotes/impl/salesloft/index.ts b/packages/core/remotes/impl/salesloft/index.ts index fb7985f2f..3c860f966 100644 --- a/packages/core/remotes/impl/salesloft/index.ts +++ b/packages/core/remotes/impl/salesloft/index.ts @@ -13,8 +13,10 @@ import type { EngagementCommonObjectType, EngagementCommonObjectTypeMap, Sequence, + SequenceCreateParams, SequenceState, SequenceStateCreateParams, + SequenceStepCreateParams, User, } from '@supaglue/types/engagement'; import axios, { AxiosError } from 'axios'; @@ -31,6 +33,8 @@ import { fromSalesloftPersonToContact, fromSalesloftUserToUser, toSalesloftAccountCreateParams, + toSalesloftCadenceImportParams, + toSalesloftCadenceStepImportParams, toSalesloftContactCreateParams, toSalesloftSequenceStateCreateParams, } from './mappers'; @@ -289,6 +293,30 @@ class SalesloftClient extends AbstractEngagementRemoteClient { return response.data.data.id.toString(); } + async #importSequence(params: SequenceCreateParams): Promise { + await this.maybeRefreshAccessToken(); + const response = await axios.post<{ data: { cadence: { id: number } } }>( + `${this.#baseURL}/v2/cadence_imports`, + toSalesloftCadenceImportParams(params), + { headers: this.#headers } + ); + return response.data.data.cadence.id.toString(); + } + + async #importSequenceStep(params: SequenceStepCreateParams): Promise { + await this.maybeRefreshAccessToken(); + + const response = await axios.post<{ data: { cadence: { id: number } } }>( + `${this.#baseURL}/v2/cadence_imports`, + toSalesloftCadenceStepImportParams(params), + { headers: this.#headers } + ); + // TODO: The response does not contain step Id... So the return value is only the cadence ID + // Should we do a fetch on the cadence instead? But the problem is we also don't have a way to + // definitively idenfiy the step we just created + return response.data.data.cadence.id.toString(); + } + public override async createCommonObjectRecord( commonObjectType: T, params: EngagementCommonObjectTypeMap['createParams'] @@ -310,6 +338,9 @@ class SalesloftClient extends AbstractEngagementRemoteClient { id: await this.#createRecord('/v2/people', toSalesloftContactCreateParams(params as ContactCreateParams)), }; case 'sequence': + return { id: await this.#importSequence(params as SequenceCreateParams) }; + case 'sequence_step': + return { id: await this.#importSequenceStep(params as SequenceStepCreateParams) }; case 'mailbox': case 'user': throw new BadRequestError(`Create operation not supported for ${commonObjectType} object in Salesloft`); diff --git a/packages/core/remotes/impl/salesloft/mappers.test.ts b/packages/core/remotes/impl/salesloft/mappers.test.ts index 79ac11e00..cd8857281 100644 --- a/packages/core/remotes/impl/salesloft/mappers.test.ts +++ b/packages/core/remotes/impl/salesloft/mappers.test.ts @@ -5,7 +5,7 @@ */ import { describe, expect, it } from '@jest/globals'; -import type { ContactCreateParams, SequenceStateCreateParams } from '@supaglue/types/engagement'; +import type { ContactCreateParams, SequenceCreateParams, SequenceStateCreateParams } from '@supaglue/types/engagement'; import { fromSalesloftAccountToAccount, fromSalesloftCadenceMembershipToSequenceState, @@ -13,6 +13,7 @@ import { fromSalesloftPersonToContact, fromSalesloftUserToUser, toSalesloftAccountCreateParams, + toSalesloftCadenceImportParams, toSalesloftContactCreateParams, toSalesloftSequenceStateCreateParams, } from './mappers'; @@ -209,6 +210,96 @@ describe('Salesloft mapper tests', () => { expect(toSalesloftAccountCreateParams(record)).toEqual(expected); }); + it('should convert toSalesloftCadenceStepImportParams', () => { + const record: SequenceCreateParams = { + name: 'Sequence 1', + type: 'private', + steps: [ + { + type: 'auto_email', + template: { + body: 'test body', + subject: 'test subject', + name: 'test template', + }, + intervalSeconds: 86400, // 1 day after sequence start + }, + { + type: 'auto_email', + template: { + body: 'test body', + subject: 'test subject', + name: 'test template', + }, + intervalSeconds: 86400 * 2, // 2 days later + }, + ], + }; + + expect(toSalesloftCadenceImportParams(record)).toMatchInlineSnapshot(` + { + "cadence_content": { + "step_groups": [ + { + "automated": true, + "automated_settings": undefined, + "day": 2, + "due_immediately": false, + "reference_id": 1, + "steps": [ + { + "enabled": true, + "name": "Step 1", + "type": "Email", + "type_settings": { + "email_template": { + "body": "test body", + "subject": "test subject", + "title": "test template", + }, + }, + }, + ], + }, + { + "automated": true, + "automated_settings": undefined, + "day": 4, + "due_immediately": false, + "reference_id": 2, + "steps": [ + { + "enabled": true, + "name": "Step 2", + "type": "Email", + "type_settings": { + "email_template": { + "body": "test body", + "subject": "test subject", + "title": "test template", + }, + }, + }, + ], + }, + ], + }, + "settings": { + "cadence_function": "outbound", + "external_identifier": null, + "name": "Sequence 1", + "remove_bounced": true, + "remove_replied": true, + "target_daily_people": 0, + }, + "sharing_settings": { + "shared": true, + "team_cadence": false, + }, + } + `); + }); + it('should convert to Salesloft Contact create params correctly', () => { const contact: ContactCreateParams = { firstName: 'John', diff --git a/packages/core/remotes/impl/salesloft/mappers.ts b/packages/core/remotes/impl/salesloft/mappers.ts index fc7ed964b..9c3a10ffe 100644 --- a/packages/core/remotes/impl/salesloft/mappers.ts +++ b/packages/core/remotes/impl/salesloft/mappers.ts @@ -6,11 +6,14 @@ import type { EmailAddress, PhoneNumber, Sequence, + SequenceCreateParams, SequenceState, SequenceStateCreateParams, + SequenceStepCreateParams, User, } from '@supaglue/types/engagement'; import { camelcaseKeys } from '@supaglue/utils'; +import { BadRequestError } from '../../../errors'; export const fromSalesloftAccountToAccount = (record: Record): Account => { return { @@ -185,3 +188,236 @@ export const toSalesloftSequenceStateCreateParams = ( user_id: sequenceState.userId, }; }; + +/** + * Issues: + * - `ownerId` does not appear to be supported by salesloft + */ +export const toSalesloftCadenceImportParams = (sequence: SequenceCreateParams): CadenceImport => { + const stepGroups: StepGroup[] = []; + let dayOffset = 0; // salesloft day is always relative to start of the sequence, so we account for that here + for (const [index, step] of (sequence.steps ?? []).entries()) { + // salesloft does not have a concept of step groups within their UI + // and in fact seems to create a group for every step anyways... So we will replicate the same behavior + const group = toSalesloftCadenceStepImportParams({ + ...step, + order: index + 1, + intervalSeconds: (step.intervalSeconds ?? 0) + dayOffset * 86400, + }).cadence_content.step_groups[0]; + dayOffset = group.day - 1; + stepGroups.push(group); + } + + return { + ...(sequence.customFields ?? {}), // settings and sharing_settings are specifically extracted below + settings: { + name: sequence.name, + target_daily_people: 0, + cadence_function: 'outbound', + remove_bounced: true, + remove_replied: true, + external_identifier: null, + ...(sequence.customFields?.settings ?? {}), + }, + sharing_settings: { + team_cadence: sequence.type === 'team', + shared: true, // the default when creating in the UI + ...(sequence.customFields?.sharing_settings ?? {}), + }, + cadence_content: { step_groups: stepGroups }, + }; +}; + +/** + * Issues: + * - `linkedin_send_message` is not natively supported + * - No ability to use existing template ID + * - Step group vs. steps + * - date / interval based sequences vs. day based sequences + * - Differentiate between auto-email vs. manual email + * - Only works if a cadence has exactly 0 steps... https://share.cleanshot.com/dVx6sqTD + */ +export const toSalesloftCadenceStepImportParams = (step: SequenceStepCreateParams): CadenceImport => { + if (step.date) { + throw new BadRequestError('Only relative delays are supported for Salesloft sequences'); + } + + const day = (step.intervalSeconds ?? 0) / 86400 + 1; + if (!Number.isInteger(day)) { + throw new BadRequestError('Salesloft only supports intervals in whole days (i.e. multiples of 86400)'); + } + + const delayInMins = Math.floor(((step.intervalSeconds ?? 0) % 86400) / 60); + + if (delayInMins > 720) { + throw new BadRequestError('Salesloft only supports delays up to 720 minutes within a day'); + } + + if (step.type === 'linkedin_send_message') { + // Not clear how to implement this at the moment. We need + // integration_id and integration_step_type_guid for this to work and it is quite Salesloft specific + // @see https://share.cleanshot.com/BY66pmLW + throw new BadRequestError('LinkedIn steps are not currently supported for Salesloft sequences'); + } + if (step.template && 'id' in step.template) { + throw new BadRequestError('Template IDs are not currently supported for Salesloft sequences'); + } + + const cadenceStep: Step | null = + (step.type === 'manual_email' || step.type === 'auto_email') && step.template && 'body' in step.template + ? { + enabled: true, + type: 'Email', + name: [step.name ?? `Step ${step.order}`, step.taskNote].filter((n) => !!n).join(': '), + type_settings: { + email_template: { title: step.template.name, subject: step.template.subject, body: step.template.body }, + }, + } + : step.type === 'call' + ? { + enabled: true, + type: 'Phone', + name: step.name ?? `Step ${step.order}`, + type_settings: { instructions: step.taskNote ?? '' }, + } + : { + enabled: true, + type: 'Other', + name: step.name ?? `Step ${step.order}`, + type_settings: { instructions: `${step.type}: ${step.taskNote ?? ''}` }, + }; + + return { + cadence_content: { + cadence_id: step.sequenceId ? parseInt(step.sequenceId, 10) : undefined, + step_groups: [ + { + day, + automated: step.type === 'auto_email', + automated_settings: + step.type === 'auto_email' && delayInMins !== 0 + ? { send_type: 'after_time_delay', delay_time: delayInMins } + : undefined, + due_immediately: false, + steps: cadenceStep ? [cadenceStep] : [], + reference_id: step.order ?? null, + }, + ], + ...step.customFields, + }, + }; +}; + +/** @see https://gist.github.com/tonyxiao/6e14c2348e4672e91257c0b918d5ccab */ +export interface CadenceImport { + /** optional when cadence_content.cadence_id is specified */ + settings?: Settings; + /** optional when cadence_content.cadence_id is specified */ + sharing_settings?: SharingSettings; + cadence_content: { + /** For importing */ + cadence_id?: number; + step_groups: StepGroup[]; + }; +} + +interface Settings { + name: string; + target_daily_people: number; + remove_replied: boolean; + remove_bounced: boolean; + remove_people_when_meeting_booked?: boolean; + external_identifier: null | string | number; + /** https://share.cleanshot.com/1JmgKzwV */ + cadence_function: 'outbound' | 'inbound' | 'event' | 'other'; +} + +interface SharingSettings { + team_cadence: boolean; + shared: boolean; +} + +interface StepGroup { + /** Collection of all the settings for an automated step. Only valid if automated is true. */ + automated_settings?: AutomatedSettings; + /** Describes if the step happens with or without human intervention. Can only be true if steps in group are Email steps. */ + automated: boolean; + /** The day that the step will be executed */ + day: number; + /** Describes if the step is due immediately or not. */ + due_immediately: boolean; + /** Used to correlate threaded email steps. Required for email step, can pass 0 for example. */ + reference_id?: number | null; + /** All of the steps that belong to a particular day */ + steps: Step[]; +} + +/** + * Represents the parameters for an automated action. Only valid for automated email steps + */ +type AutomatedSettings = { + /** Determines whether or not the step is able to be sent on weekends */ + allow_send_on_weekends?: boolean; +} & ( + | { + /** + * Describes if the step is due immediately or not. + * Must be either "at_time" or "after_time_delay". + */ + send_type: 'at_time'; + + /** The time that the automated action will happen. e.g. 09:00 */ + time_of_day: string; + + /** + * Specifies whether the email is sent after the person's timezone + * or the user's timezone. + * Must be either "person" or "user". + */ + timezone_mode: 'person' | 'user'; + } + | { + /** + * Describes if the step is due immediately or not. + * Must be either "at_time" or "after_time_delay". + */ + send_type: 'after_time_delay'; + + /** must be a number between 0 and 720 (minutes */ + delay_time: number; + } +); + +type Step = { + /** Describes if that step is currently enabled */ + enabled: boolean; + /** The name given by the user for the step */ + name: string; +} & ( + | { type: 'Phone'; type_settings: { instructions: string } } + | { type: 'Other'; type_settings: { instructions: string } } + | { + type: 'Integration'; + type_settings: { + /** The instructions to follow when executing that step */ + instructions: string; + /** Identifies the Salesloft integration you are trying to use */ + integration_id: number; + /** For LinkedIn steps, identifies one of the LinkedIn Steps. */ + integration_step_type_guid: string; + }; + } + | { + type: 'Email'; + type_settings: { + /** Used to reference the step group of the previous email in a thread */ + previous_email_step_group_reference_id?: number; + /** Content for the email template used in this step */ + email_template?: { + title?: string; + subject?: string; + body?: string; + }; + }; + } +); diff --git a/packages/schemas/gen/v2/engagement.ts b/packages/schemas/gen/v2/engagement.ts index c12b6402a..920aa5181 100644 --- a/packages/schemas/gen/v2/engagement.ts +++ b/packages/schemas/gen/v2/engagement.ts @@ -401,10 +401,13 @@ export interface components { */ type: "team" | "private"; owner_id?: string; + steps?: (components["schemas"]["create_sequence_step"])[]; custom_fields?: components["schemas"]["custom_fields"]; }; create_sequence_step: { - /** @description The interval (in seconds) until this step will activate; only applicable to interval-based sequences. */ + /** @description The name given by the user for the step. Used by Salesloft only. */ + name?: string; + /** @description The interval (in seconds) until this step will activate after the previous step (in case of first step, relative to when prospect first enters a sequence); only applicable to interval-based sequences. This is 0 by default */ interval_seconds?: number; /** * @description The date this step will activate; only applicable to date-based sequences. @@ -412,7 +415,7 @@ export interface components { */ date?: string; /** @description The email/message template to be used for this step. Only applicable for email or message steps. */ - template: OneOf<[{ + template?: OneOf<[{ /** @description The ID of the template to use for this step. */ id: string; }, { @@ -431,9 +434,9 @@ export interface components { custom_fields?: components["schemas"]["custom_fields"]; }]>; /** @description If true, this step will be sent as a reply to the previous step. */ - is_reply: boolean; - /** @description The step's display order within its sequence. */ - order: number; + is_reply?: boolean; + /** @description The step's display order within its sequence. Only applicable for Outreach when adding steps one at a time after the initial sequence creation, otherwise when creating steps together with sequence order is implicit based on the order of step within the step array. Salesloft does not use the `order` param, and order is instead determined by `interval_seconds` which translates into the `day` parameter */ + order?: number; /** * @description The type of the sequence state. Note: `linkedin_send_message` is undocumented in Outreach and subject to change. * diff --git a/packages/types/engagement/sequence.ts b/packages/types/engagement/sequence.ts index 35fce6929..1ed41e92c 100644 --- a/packages/types/engagement/sequence.ts +++ b/packages/types/engagement/sequence.ts @@ -1,5 +1,6 @@ import type { SnakecasedKeys } from '../snakecased_keys'; import type { BaseEngagementModel, SnakecasedEngagementTenantFields } from './base'; +import type { SequenceStepCreateParams } from './sequence_step'; export type SnakecasedKeysSequence = SnakecasedKeys; export type SnakecasedKeysSequenceWithTenant = SnakecasedKeysSequence & SnakecasedEngagementTenantFields; @@ -26,5 +27,6 @@ export type SequenceCreateParams = { ownerId?: string; tags?: string[]; type: 'team' | 'private'; + steps?: SequenceStepCreateParams[]; customFields?: Record; }; diff --git a/packages/types/engagement/sequence_step.ts b/packages/types/engagement/sequence_step.ts index 67266d31a..96332d337 100644 --- a/packages/types/engagement/sequence_step.ts +++ b/packages/types/engagement/sequence_step.ts @@ -13,12 +13,14 @@ type CoreSequenceStep = { export type SequenceStep = BaseEngagementModel & CoreSequenceStep; export type SequenceStepCreateParams = { - sequenceId: string; - order: number; + /** Can be empty when creating steps together with Sequence */ + sequenceId?: string; + order?: number; + name?: string; date?: string; intervalSeconds?: number; - isReply: boolean; - template: SequenceTemplateId | SequenceTemplateCreateParams; + isReply?: boolean; + template?: SequenceTemplateId | SequenceTemplateCreateParams; type: 'auto_email' | 'manual_email' | 'task' | 'call' | 'linkedin_send_message'; taskNote?: string; customFields?: Record; diff --git a/yarn.lock b/yarn.lock index 9fa2411a8..4a08ea9c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8253,6 +8253,7 @@ __metadata: resolution: "api@workspace:apps/api" dependencies: "@godaddy/terminus": ^4.11.2 + "@hapi/boom": ^10.0.1 "@sentry/integrations": ^7.64.0 "@sentry/node": ^7.64.0 "@supaglue/core": "workspace:*"