import type {ReadonlyDeep} from 'type-fest'; import type {Knex} from 'knex'; export interface Pagination { page: number; pages: number; limit: number | 'all'; total: number; prev: number | null; next: number | null; } export interface Page { data: T[]; meta: { pagination: Pagination; }; } export interface WaitAction { id: string; type: 'wait'; data: { wait_hours: number; }; } export interface AutomationEmailStats { email_clicked_count: number; email_sent_count: number; email_opened_count: number; opened_rate: number | null; clicked_rate: number | null; } export interface AutomationActionLink { url: string; clicked_count: number; } export interface SendEmailAction { id: string; type: 'send_email'; data: { email_subject: string; email_lexical: string; email_design_setting_id: string; }; stats?: AutomationEmailStats; } export type AutomationAction = WaitAction | SendEmailAction; export interface AutomationEdge { source_action_id: string; target_action_id: string; } export interface AutomationSummary { id: string; slug: string; name: string; status: string; created_at: string; updated_at: string; } export interface Automation extends AutomationSummary { actions: AutomationAction[]; edges: AutomationEdge[]; } export interface EditAutomationData { status: string; actions: AutomationAction[]; edges: AutomationEdge[]; } export type AutomatedEmailRecipientWithMailgunId = { id: string; mailgun_message_id: string; automation_action_revision_id: string; }; export type AutomatedEmailEvents = { deliveredAt?: Date; openedAt?: Date; automationActionRevisionId: string; }; export type RecordEmailSentOptions = Readonly<{ automationActionRevisionId: string; automationRunStepId: string; mailgunMessageId?: string; memberEmail: string; memberId: string; memberName: string | null; memberUuid: string; trackClicks: boolean; trackOpens: boolean; }>; type AutomationStepBase = { id: string; locked_by: string; automation_run_id: string; automation_id: string; // NOTE: This property will be removed once we support additional automation triggers. automation_slug: string; automation_status: 'inactive' | 'active'; member_id: string | null; member_email: string; action_id: string; automation_action_revision_id: string; ready_at: Date; step_attempts: number; }; export type AutomationStepToRun = ReadonlyDeep; export type AutomationStepTerminalStatus = | 'automation disabled' | 'failed' | 'finished' | 'member changed status' | 'member unsubscribed'; export interface AutomationsRepository { browse(): Promise>; getById(id: string): Promise; getAutomationActionLinks(automationId: string, actionId: string): Promise; edit(id: string, data: EditAutomationData): Promise; trigger(options: { memberEmail: string; memberId: string; memberStatus: 'free' | 'paid'; }): Promise; /** * Select the steps we want to run and return the next time any remaining * pending step should be polled, if any. * * If we could guarantee this function would only be called once ever, it'd * be pretty simple! However, we want to handle cases where this function is * called multiple times simultaneously (maybe in different processes * querying the same database). That's why we implement a row-level locking * mechanism, hence the word "lock" in the name of this function. */ fetchAndLockSteps(limit: number): Promise<{ steps: AutomationStepToRun[], nextStepReadyAt: Date | null; }>; /** * Atomically finish a locked step and create the next one so concurrent * runners cannot enqueue duplicates. * * Returns the next step's ready time, or null if no next step was created. */ finishStepAndEnqueueNext( step: Pick ): Promise; /** * Stop a locked step without continuing the automation, preserving the * reason it ended. * * Returns whether the step was updated. */ markStepTerminal( step: Pick, status: AutomationStepTerminalStatus ): Promise; /** * Put a locked step back in the queue for another attempt. * * Returns whether the step was updated. */ retryStep( step: Pick, retryAt: Readonly ): Promise; /** * Record a sent email and increment its action revision's sent count. */ recordEmailSent(options: RecordEmailSentOptions): Promise; /** * Fetch sent emails by their Mailgun IDs. */ getAutomatedEmailRecipientsByMailgunIds( mailgunMessageIds: ReadonlyArray ): Promise; /** * Track delivery and open events. */ trackEmailDeliveredAndOpened( eventsByAutomatedEmailRecipientId: ReadonlyDeep> ): Promise; /** * Record the first click timestamp for the automated email recipient identified by a run step. * */ trackEmailClicked(options: { automationActionRevisionId: string; automationRunStepId: string; memberId: string; clickedAt: Readonly; }, transactionOptions?: { transacting?: Knex.Transaction; }): Promise; }