Files
govoplan-campaign/webui/src/api/campaigns.ts
T
zemion c51fc180fb
Module Package Release / publish-packages (push) Successful in 12s
Release govoplan-campaign v0.1.28: stabilize saving, review and delivery recovery
2026-09-08 01:32:26 +02:00

2191 lines
67 KiB
TypeScript

import type { ApiSettings, CampaignListItem, DeltaDeletedItem } from "../types";
import { apiDownload, apiFetch } from "./client";
import {
apiReferenceOptionProvider,
type ReferenceOptionProvider
} from "@govoplan/core-webui";
import { campaignJobsQueryParams, type CampaignJobsQueryParameters } from "../features/campaigns/utils/jobListQuery";
import { campaignVersionUpdateForRequest } from "../features/campaigns/utils/editorState";
export {
fetchResourceAccessExplanation,
fetchResourceAccessExplanationSubjects
} from "@govoplan/core-webui";
export type {
AccessDecisionProvenanceItem,
ResourceAccessExplanationUser as AccessExplanationUser,
ResourceAccessExplanationResponse,
ResourceAccessExplanationSubjectsResponse
} from "@govoplan/core-webui";
export type CampaignListResponse =
CampaignListItem[] |
{
campaigns?: CampaignListItem[];
items?: CampaignListItem[];
results?: CampaignListItem[];
};
export type CampaignShare = {
id: string;
campaign_id: string;
target_type: "user" | "group";
target_id: string;
permission: "read" | "write";
revoked_at?: string | null;
};
export type CampaignShareTarget = {id: string;name: string;secondary?: string | null;};
export type CampaignShareTargets = {users: CampaignShareTarget[];groups: CampaignShareTarget[];};
export type CampaignCollaborationReferenceKind =
| "campaign_version"
| "recipient_import_batch"
| "attachment_rule"
| "delivery_job"
| "report";
export type CampaignCollaborationReference = {
kind: CampaignCollaborationReferenceKind;
id: string;
label?: string | null;
};
export type CampaignCollaborationEntry = {
id: string;
campaign_id: string;
actor_user_id?: string | null;
actor_label: string;
visibility: "collaborators" | "moderators";
content?: string | null;
content_sha256: string;
mention_user_ids: string[];
reference?: CampaignCollaborationReference | null;
tombstone?: "withdrawn" | "redacted" | null;
tombstone_reason?: string | null;
withdrawn_at?: string | null;
redacted_at?: string | null;
created_at: string;
};
export type CampaignCollaborationListResponse = {
items: CampaignCollaborationEntry[];
next_cursor?: string | null;
has_more: boolean;
};
export type CampaignCollaborationCreate = {
content: string;
visibility?: CampaignCollaborationEntry["visibility"];
reference?: Pick<CampaignCollaborationReference, "kind" | "id"> | null;
mention_user_ids?: string[];
};
export type CampaignWorkAssigneeType = "account" | "group" | "organization_function";
export type CampaignWorkAssignmentStatus = "open" | "in_progress" | "completed" | "rejected" | "cancelled";
export type CampaignWorkAssignmentResolutionState = "resolved" | "unavailable" | "provider_unavailable";
export type CampaignWorkAssignment = {
id: string;
campaign_id: string;
purpose: string;
status: CampaignWorkAssignmentStatus;
due_at?: string | null;
assignee_type: CampaignWorkAssigneeType;
assignee_id: string;
assignee_label_snapshot: string;
assignee_current_label?: string | null;
assignee_resolution_state: CampaignWorkAssignmentResolutionState;
resolution_provenance: Record<string, unknown>;
resolution_checked_at: string;
assigned_by_user_id?: string | null;
assigned_by_label: string;
reference?: CampaignCollaborationReference | null;
completed_at?: string | null;
cancelled_at?: string | null;
task_mirror_id?: string | null;
task_mirror_status: "not_configured" | "mirrored" | "failed" | "skipped";
task_mirror_error?: string | null;
resource_revision: number;
created_at: string;
updated_at: string;
};
export type CampaignWorkAssignmentListResponse = {
items: CampaignWorkAssignment[];
next_cursor?: string | null;
has_more: boolean;
};
export type CampaignWorkAssignmentEvent = {
id: string;
assignment_id: string;
event_kind: string;
actor_user_id?: string | null;
actor_label: string;
status: CampaignWorkAssignmentStatus;
assignee_type: CampaignWorkAssigneeType;
assignee_id: string;
assignee_label: string;
resolution_state: CampaignWorkAssignmentResolutionState;
details: Record<string, unknown>;
created_at: string;
};
export type CampaignWorkAssignmentHistoryResponse = {
items: CampaignWorkAssignmentEvent[];
next_cursor?: string | null;
has_more: boolean;
};
export type CampaignArchiveEncryptionPolicy = {
available: boolean;
allowed_password_encryption_methods: Array<"aes" | "zip_standard">;
allowed_password_delivery_channels: Array<"separate_mail" | "sms" | "letter" | "phone" | "in_person">;
policy_hash: string;
source_path: Array<{
scope_type: string;
scope_id?: string | null;
path: string;
label: string;
applied_fields: string[];
policy: Record<string, unknown>;
}>;
reason: string;
diagnostics: Array<Record<string, unknown>>;
legacy_label: string;
};
export type CampaignUpdatePayload = {
external_id?: string | null;
name?: string | null;
status?: string | null;
description?: string | null;
};
export type CampaignCreateMinimalPayload = {
external_id?: string;
name?: string;
description?: string | null;
current_flow?: string;
current_step?: string;
};
export type CampaignCreateResponse = {
campaign: CampaignListItem & {
current_version_id?: string | null;
};
version: CampaignVersionListItem;
};
export type CampaignVersionListItem = {
id: string;
campaign_id: string;
version_number: number;
edit_revision: number;
strong_etag: string;
schema_version?: string;
source_filename?: string | null;
source_base_path?: string | null;
workflow_state?: string;
current_flow?: string;
current_step?: string | null;
is_complete?: boolean;
editor_state?: Record<string, unknown>;
autosaved_at?: string | null;
published_at?: string | null;
locked_at?: string | null;
locked_by_user_id?: string | null;
user_lock_state?: "temporary" | "permanent" | null;
user_locked_at?: string | null;
user_locked_by_user_id?: string | null;
created_at?: string;
updated_at?: string;
validation_summary?: Record<string, unknown> | null;
build_summary?: Record<string, unknown> | null;
execution_snapshot_hash?: string | null;
execution_snapshot_at?: string | null;
delivery_mode?: "synchronous" | "worker_queue" | "database_queue" | null;
delivery_mode_selected_at?: string | null;
archived_at?: string | null;
archived_by_user_id?: string | null;
};
export type CampaignLifecycleAction = {
allowed: boolean;
reason?: string | null;
};
export type CampaignLifecyclePolicy = {
policy_id: string;
policy_version: string;
state_token: string;
actions: Record<string, CampaignLifecycleAction>;
provenance: Record<string, unknown>;
};
export type CampaignCopyOptions = {
name?: string;
external_id?: string;
include_recipients: boolean;
include_files: boolean;
include_shares: boolean;
include_policies: boolean;
include_mail_profile: boolean;
};
export type CampaignTransferScope =
| "metadata"
| "template_config"
| "recipients"
| "attachments"
| "review_state"
| "delivery_history";
export type CampaignPortablePackage = {
format: "govoplan.campaign-portable";
format_version: string;
package_id: string;
exported_at: string;
source: Record<string, unknown>;
scopes: CampaignTransferScope[];
manifest: Record<string, unknown>;
payload: Record<string, unknown>;
integrity: { algorithm: string; package_sha256: string };
};
export type CampaignTransferPlanItem = {
scope: CampaignTransferScope;
code: string;
summary: string;
item_count?: number | null;
};
export type CampaignImportPreview = {
compatible: boolean;
package_id?: string | null;
package_sha256?: string | null;
format_version?: string | null;
source: Record<string, unknown>;
available_scopes: CampaignTransferScope[];
selected_scopes: CampaignTransferScope[];
destination: { external_id?: string; name?: string; status?: string };
will_create: CampaignTransferPlanItem[];
will_skip: CampaignTransferPlanItem[];
warnings: string[];
errors: string[];
};
export type CampaignImportApplyResponse = CampaignCreateResponse & {
receipt: Record<string, unknown>;
};
export type CampaignScheduleOccurrence = {
id: string;
schedule_id: string;
scheduled_for: string;
status: "prepared" | "accepted" | "uncertain" | "failed" | "skipped" | "superseded" | string;
idempotency_key?: string | null;
generated_campaign_id?: string | null;
generated_version_id?: string | null;
error?: string | null;
delivery_command_ids: string[];
recovery_state: string;
evidence: Record<string, unknown>;
last_checked_at?: string | null;
created_at: string;
};
export type CampaignSchedule = {
id: string;
campaign_id: string;
source_version_id: string;
name: string;
delivery_mode: "manual" | "autonomous";
recurrence_kind: "once" | "daily" | "weekly" | "monthly";
interval_count: number;
timezone: string;
starts_at: string;
next_fire_at?: string | null;
ends_at?: string | null;
max_occurrences: number;
occurrence_count: number;
active: boolean;
resource_revision: number;
last_fired_at?: string | null;
last_campaign_id?: string | null;
last_error?: string | null;
last_outcome?: string | null;
last_recovery_state?: string | null;
created_at: string;
updated_at: string;
occurrences: CampaignScheduleOccurrence[];
};
export type CampaignScheduleCreate = {
source_version_id: string;
name: string;
delivery_mode: CampaignSchedule["delivery_mode"];
recurrence_kind: CampaignSchedule["recurrence_kind"];
interval_count: number;
timezone: string;
starts_at: string;
ends_at?: string | null;
max_occurrences: number;
include_recipients: boolean;
include_files: boolean;
include_shares: boolean;
include_policies: boolean;
include_mail_profile: boolean;
};
export type CampaignVersionDetail = CampaignVersionListItem & {
review_build_token?: string | null;
raw_json: Record<string, unknown>;
campaign_json?: Record<string, unknown>;
mail_profile_migration_required?: boolean;
};
export type CampaignWorkspaceResponse = {
campaign: CampaignListItem | null;
versions: CampaignVersionListItem[];
current_version: CampaignVersionDetail | null;
summary: CampaignSummary | null;
selected_version_id?: string | null;
};
export type CampaignDeltaResponse = {
campaigns: CampaignListItem[];
deleted: DeltaDeletedItem[];
watermark?: string | null;
has_more: boolean;
full: boolean;
total: number;
page: number;
page_size: number;
pages: number;
};
export type CampaignWorkspaceDeltaResponse = CampaignWorkspaceResponse & {
deleted: DeltaDeletedItem[];
watermark?: string | null;
has_more: boolean;
full: boolean;
};
export type CampaignWorkspaceQuery = {
versionId?: string | null;
includeCurrentVersion?: boolean;
includeSummary?: boolean;
includeVersions?: boolean;
since?: string | null;
limit?: number;
};
export type RecipientImportColumnKind = "ignore" | "id" | "active" | "name" | "from" | "to" | "cc" | "bcc" | "reply_to" | "field" | "new_field" | "attachment_pattern";
export type RecipientImportColumnMappingProfileItem = {
columnIndex: number;
kind: RecipientImportColumnKind;
fieldName?: string | null;
newFieldName?: string | null;
};
export type RecipientImportMappingProfile = {
id: string;
name: string;
createdAt: string;
updatedAt: string;
columnCount: number;
headers: string[];
normalizedHeaders: string[];
orderedHeaderFingerprint: string;
unorderedHeaderFingerprint: string;
delimiter: "," | ";" | "\t";
headerRows: number;
quoted: boolean;
valueSeparators: string;
mappings: RecipientImportColumnMappingProfileItem[];
};
export type RecipientImportMappingProfilePayload = Omit<RecipientImportMappingProfile, "id" | "createdAt" | "updatedAt">;
export type RecipientImportMappingProfileListResponse = {
profiles: RecipientImportMappingProfile[];
};
export type CampaignAddressLookupCandidate = {
contact_id: string;
address_book_id: string;
display_name: string;
email?: string | null;
email_label?: string | null;
organization?: string | null;
role_title?: string | null;
tags: string[];
source_kind: string;
source_ref?: string | null;
source_revision?: string | null;
provenance: Record<string, unknown>;
};
export type CampaignAddressLookupResponse = {
available: boolean;
candidates: CampaignAddressLookupCandidate[];
};
export type CampaignRecipientAddressSource = {
source_id: string;
source_label: string;
source_kind: string;
source_revision: string;
recipient_count: number;
provenance: Record<string, unknown>;
};
export type CampaignRecipientAddressSourcesResponse = {
available: boolean;
sources: CampaignRecipientAddressSource[];
};
export type CampaignDistributionListParameter = {
key: string;
value_type: "string" | "integer" | "number" | "boolean" | "date" | "datetime" | "string_list";
label?: string | null;
required: boolean;
default?: unknown;
allowed_values: unknown[];
minimum?: number | null;
maximum?: number | null;
pattern?: string | null;
description?: string | null;
};
export type CampaignDistributionListSource = {
id: string;
tenant_id: string;
name: string;
revision_id: string;
revision: number;
definition_hash: string;
definition_kind: string;
description?: string | null;
status: string;
entry_count: number;
read_only: boolean;
stale: boolean;
parameters: CampaignDistributionListParameter[];
updated_at?: string | null;
provenance: Record<string, unknown>;
metadata: Record<string, unknown>;
};
export type CampaignDistributionListSourcesResponse = {
available: boolean;
expand_available: boolean;
sources: CampaignDistributionListSource[];
};
export type CampaignDistributionSourceReference = {
provider: string;
resource_type: string;
resource_id: string;
revision?: string | null;
fingerprint?: string | null;
label?: string | null;
metadata: Record<string, unknown>;
};
export type CampaignDistributionExplanation = {
code: string;
message: string;
severity: "info" | "warning" | "error" | string;
provider?: string | null;
source?: CampaignDistributionSourceReference | null;
provenance: Record<string, unknown>;
};
export type CampaignDistributionChannelCandidate = {
channel: "email" | "postal" | "internal_mail" | "portal" | string;
target: string;
target_key: string;
status: string;
contact_point_id?: string | null;
locale?: string | null;
preferred: boolean;
reason_code?: string | null;
explanation?: string | null;
source?: CampaignDistributionSourceReference | null;
decision_provenance: Record<string, unknown>;
};
export type CampaignDistributionRecipient = {
recipient_key: string;
display_name: string;
status: string;
channels: CampaignDistributionChannelCandidate[];
identity_id?: string | null;
account_id?: string | null;
contact_id?: string | null;
organization_unit_id?: string | null;
function_id?: string | null;
source_entry_ids: string[];
explanations: CampaignDistributionExplanation[];
attributes: Record<string, unknown>;
provenance: Record<string, unknown>;
};
export type CampaignDistributionProviderEvidence = {
provider: string;
source: CampaignDistributionSourceReference;
actual_revision?: string | null;
actual_fingerprint?: string | null;
stale: boolean;
generated_at?: string | null;
details: Record<string, unknown>;
};
export type CampaignDistributionListExpansion = {
source: CampaignDistributionListSource;
request: Record<string, unknown>;
recipients: CampaignDistributionRecipient[];
excluded: CampaignDistributionRecipient[];
diagnostics: CampaignDistributionExplanation[];
provider_evidence: CampaignDistributionProviderEvidence[];
expansion_hash: string;
generated_at?: string | null;
snapshot_id?: string | null;
stale: boolean;
truncated: boolean;
};
export type CampaignDistributionListExpansionInput = {
list_id: string;
revision?: number | null;
effective_at?: string | null;
purpose?: string;
requested_channels: Array<"email" | "postal" | "internal_mail" | "portal">;
parameters: Record<string, unknown>;
idempotency_key?: string | null;
};
export type CampaignPostboxDirectoryEntry = {
id: string;
address: string;
address_key: string;
name: string;
status: string;
classification: string;
organization_unit_id?: string | null;
organization_unit_name?: string | null;
function_id?: string | null;
function_name?: string | null;
context_key?: string | null;
holder_count: number;
vacant: boolean;
};
export type CampaignPostboxTemplate = {
id: string;
slug: string;
name: string;
description?: string | null;
published_revision_id: string;
function_type_id?: string | null;
scope_kind: string;
scope_id?: string | null;
classification: string;
allow_vacant_delivery: boolean;
};
export type CampaignPostboxFunction = {
id: string;
slug: string;
name: string;
function_type_id?: string | null;
};
export type CampaignPostboxOrganizationUnit = {
id: string;
slug: string;
name: string;
unit_type_id?: string | null;
parent_id?: string | null;
functions: CampaignPostboxFunction[];
};
export type CampaignPostboxCatalog = {
available: boolean;
postboxes: CampaignPostboxDirectoryEntry[];
templates: CampaignPostboxTemplate[];
organization_units: CampaignPostboxOrganizationUnit[];
};
export type CampaignCalendarCatalogEntry = {
id: string;
name: string;
color?: string | null;
timezone: string;
source_kind: string;
writable: boolean;
};
export type CampaignCalendarCatalog = {
available: boolean;
reason?: string | null;
calendars: CampaignCalendarCatalogEntry[];
};
export type CampaignPrintTemplateOutputProfile = {
id: string;
label: string;
output_format: "html" | "text";
media_type: string;
channel: string;
capabilities: string[];
page: Record<string, unknown>;
};
export type CampaignPrintTemplate = {
id: string;
name: string;
slug: string;
template_type: string;
status: string;
current_revision: number;
current_revision_id: string;
published_revision_id?: string | null;
description?: string | null;
read_only: boolean;
revision?: {
revision: number;
output_profiles: CampaignPrintTemplateOutputProfile[];
required_fields: Array<{path: string;label?: string | null;required: boolean;}>;
} | null;
};
export type CampaignPrintTemplatesResponse = {
available: boolean;
templates: CampaignPrintTemplate[];
};
export type CampaignContentLibraryTarget = "subject" | "text" | "html";
export type CampaignContentLibraryItem = {
id: string;
name: string;
description?: string | null;
template_type: string;
kind: "fragment" | "campaign_part";
status: string;
scope_type: "tenant" | "group" | "user";
scope_id?: string | null;
read_only: boolean;
current_revision: number;
revision: number;
revision_id: string;
locale?: string | null;
published: boolean;
targets: CampaignContentLibraryTarget[];
subject?: string | null;
text?: string | null;
html?: string | null;
body_mode: "text" | "html" | "both";
required_fields: Array<{
path: string;
value_type: "string" | "integer" | "number" | "boolean" | "date" | "datetime" | "object" | "array";
label?: string | null;
required: boolean;
description?: string | null;
}>;
};
export type CampaignContentLibraryResponse = {
available: boolean;
writable: boolean;
reason?: string | null;
items: CampaignContentLibraryItem[];
};
export type CampaignContentLibrarySaveInput = {
name: string;
description?: string | null;
kind: "fragment" | "campaign_part";
target?: CampaignContentLibraryTarget | null;
subject?: string | null;
text?: string | null;
html?: string | null;
body_mode: "text" | "html" | "both";
locale: string;
visibility: "personal" | "tenant";
};
export type CampaignRecipientSnapshotItem = {
contact_id: string;
display_name: string;
email: string;
email_label?: string | null;
fields: Record<string, unknown>;
provenance: Record<string, unknown>;
};
export type CampaignRecipientSnapshotExcludedItem = {
contact_id: string;
display_name: string;
channel: string;
target: string;
contact_point_id?: string | null;
status: string;
reason_code?: string | null;
explanation?: string | null;
provenance: Record<string, unknown>;
};
export type CampaignRecipientAddressSourceSnapshot = {
source_id: string;
source_label: string;
source_kind: string;
source_revision: string;
generated_at: string;
recipients: CampaignRecipientSnapshotItem[];
excluded: CampaignRecipientSnapshotExcludedItem[];
included_count: number;
excluded_count: number;
purpose: string;
provenance: Record<string, unknown>;
};
export type CampaignVersionUpdatePayload = {
campaign_json?: Record<string, unknown> | null;
current_flow?: string | null;
current_step?: string | null;
workflow_state?: string | null;
is_complete?: boolean | null;
editor_state?: Record<string, unknown> | null;
source_filename?: string | null;
source_base_path?: string | null;
migrate_legacy_mail_settings?: boolean;
base_revision?: number;
reconciliation_kind?: "none" | "auto_merge" | "manual";
resolved_conflict_paths?: string[];
};
export type CampaignPartialValidationPayload = {
campaign_json?: Record<string, unknown> | null;
section?: string | null;
};
export type CampaignPartialValidationResponse = {
ok: boolean;
section?: string | null;
error_count: number;
warning_count: number;
info_count: number;
issues: Record<string, unknown>[];
};
export type CampaignRetentionReport = {
policy_status: "configured" | "defaults" | "unavailable" | string;
policy_reason?: string | null;
effective_policy: {
store_raw_campaign_json?: boolean;
raw_campaign_json_retention_days?: number | null;
generated_eml_retention_days?: number | null;
stored_report_detail_retention_days?: number | null;
mock_mailbox_retention_days?: number | null;
audit_detail_retention_days?: number | null;
audit_detail_level?: string;
};
sources: Array<{
scope_type?: string | null;
path?: string | null;
label?: string | null;
applied_fields?: string[];
}>;
evidence: Record<string, {
state?: string;
redacted_at?: string | null;
retained_count?: number;
expired_count?: number;
not_generated_count?: number;
currently_readable_count?: number;
withdrawn_count?: number;
redacted_summary_count?: number;
summary_count?: number;
}>;
privacy_impact: {
state: string;
summary: string;
retained_categories: string[];
minimized_categories: string[];
};
};
export type CampaignSummary = {
generated_at?: string;
selected_version_id?: string | null;
campaign?: CampaignListItem;
current_version?: {
id: string;
version_number?: number;
schema_version?: string;
source_filename?: string | null;
created_at?: string | null;
validation_summary?: Record<string, unknown> | null;
build_summary?: Record<string, unknown> | null;
delivery_mode?: "synchronous" | "worker_queue" | "database_queue" | null;
delivery_mode_selected_at?: string | null;
} | null;
cards?: {
jobs_total?: number;
inactive?: number;
queueable?: number;
queueable_unattempted?: number;
retryable?: number;
cancellable?: number;
needs_attention?: number;
sent?: number;
smtp_accepted?: number;
postbox_accepted?: number;
print_accepted?: number;
failed?: number;
outcome_unknown?: number;
not_attempted?: number;
skipped?: number;
queued_or_active?: number;
cancelled?: number;
partially_completed?: boolean;
imap_appended?: number;
imap_failed?: number;
imap_skipped?: number;
};
status_counts?: Record<string, Record<string, number>>;
issues?: Record<string, unknown>;
attachments?: Record<string, unknown>;
attempts?: Record<string, unknown>;
postbox_receipts?: Record<string, unknown>;
retention?: CampaignRetentionReport;
delivery?: Record<string, unknown>;
recent_failures?: Record<string, unknown>[];
};
export type CampaignQueuePayload = {
version_id?: string | null;
include_warnings?: boolean;
enqueue_celery?: boolean;
dry_run?: boolean;
};
export type CampaignDeliveryOptions = {
campaign_id: string;
version_id: string;
worker_queue_available: boolean;
postbox_available: boolean;
approval_gate: {
configured?: boolean;
available?: boolean;
approved?: boolean;
state?: string;
explanation?: string | null;
request_id?: string;
request_revision?: number;
subject_digest?: string;
};
synchronous_send: {
allowed?: boolean;
reason?: string | null;
message?: string | null;
eligible_recipient_job_count?: number;
policy?: {
max_recipient_jobs?: number;
source?: string;
deployment_max_recipient_jobs?: number;
tenant_max_recipient_jobs?: number | null;
};
};
};
export type CampaignApprovalRequestPayload = {
title: string;
description?: string | null;
steps: Array<{
key: string;
label: string;
selectors: Array<{
kind: "account" | "group" | "role" | "function_assignment" | "any_account";
value: string;
}>;
required_approvals: number;
rejection_policy: "fail_fast" | "collect";
signature_required: boolean;
forbidden_evidence_roles: Array<"author" | "owner" | "validator" | "builder" | "reviewer">;
}>;
unique_actors_across_steps: boolean;
policy_refs: string[];
idempotency_key: string;
};
export type CampaignSendNowPayload = {
version_id?: string | null;
include_warnings?: boolean;
check_files?: boolean;
validate_before_send?: boolean;
build_before_send?: boolean;
dry_run?: boolean;
use_rate_limit?: boolean;
enqueue_imap_task?: boolean;
};
export type CampaignAppendSentPayload = {
version_id?: string;
dry_run?: boolean;
enqueue_celery?: boolean;
run_inline?: boolean;
};
export type CampaignAttachmentPreviewFile = {
id: string;
version_id?: string;
blob_id?: string;
display_path: string;
filename: string;
owner_type: string;
owner_id: string;
checksum_sha256?: string;
size_bytes?: number;
content_type?: string | null;
linked_to_campaign?: boolean;
share?: Record<string, unknown> | null;
};
export type CampaignAttachmentPreviewRule = {
source: "global" | "entry";
entry_index: number;
entry_id?: string | null;
index: number;
attachment_id?: string | null;
label?: string | null;
required: boolean;
pattern: string;
base_path_name?: string | null;
base_path?: string | null;
status: "ok" | "missing" | "ambiguous";
behavior?: string | null;
zip_included?: boolean;
zip_mode?: "inherit" | "include" | "exclude";
zip_archive_id?: string | null;
zip_filename?: string | null;
matches: CampaignAttachmentPreviewFile[];
match_count: number;
linked_match_count?: number;
unlinked_match_count?: number;
issues: Record<string, unknown>[];
};
export type CampaignAttachmentPreviewResponse = {
campaign_id: string;
version_id: string;
shared_file_count: number;
candidate_file_count?: number;
matched_file_count?: number;
linked_file_count?: number;
unlinked_file_count?: number;
rules: CampaignAttachmentPreviewRule[];
linkable_files?: CampaignAttachmentPreviewFile[];
unused_shared_files: CampaignAttachmentPreviewFile[];
};
export type CampaignAttachmentPreviewPayload = {
include_unmatched?: boolean;
include_unlinked_candidates?: boolean;
campaign_json?: Record<string, unknown>;
};
export type CampaignAttachmentLinkMatchesPayload = {
campaign_json?: Record<string, unknown> | null;
dry_run?: boolean;
};
export type CampaignAttachmentLinkMatchesResponse = {
campaign_id: string;
version_id: string;
matched_file_count: number;
already_linked_file_count: number;
linked_file_count: number;
dry_run?: boolean;
linked_files: CampaignAttachmentPreviewFile[];
linkable_files: CampaignAttachmentPreviewFile[];
};
export type CampaignMockSendPayload = {
use_reviewed_build?: boolean;
version_id?: string | null;
send?: boolean;
include_warnings?: boolean;
include_needs_review?: boolean;
append_sent?: boolean;
clear_mailbox?: boolean;
check_files?: boolean;
};
export type CampaignReviewStatePayload = {
merge_progress?: boolean;
build_token?: string | null;
base_revision?: number | null;
decision_category_key?: string | null;
inspection_complete: boolean;
reviewed_message_keys: string[];
issue_decisions: Array<{
job_id: string;
decision: "accept";
reason?: string | null;
}>;
};
export type CampaignSendJobPayload = {
kind: "test" | "single_send" | "single_resend";
idempotency_key: string;
reason?: string;
context?: Record<string, unknown>;
include_warnings?: boolean;
use_rate_limit?: boolean;
enqueue_imap_task?: boolean;
};
export type CampaignJobsQuery = Omit<CampaignJobsQueryParameters, "since" | "limit">;
export type CampaignJobsResponse = {
jobs: Record<string, unknown>[];
page: number;
page_size: number;
total: number;
total_unfiltered: number;
pages: number;
cursor?: string | null;
next_cursor?: string | null;
counts: Record<string, Record<string, number>>;
filtered_counts: Record<string, Record<string, number>>;
review: {
inspection_complete?: boolean;
blocking_count?: number;
required_count?: number;
reviewed_required_count?: number;
bulk_acceptable_count?: number;
};
};
export type CampaignJobsDeltaResponse = CampaignJobsResponse & {
deleted: DeltaDeletedItem[];
watermark?: string | null;
has_more: boolean;
full: boolean;
};
export type CampaignJobDetailResponse = {
job: Record<string, unknown>;
attempts: {
smtp?: Record<string, unknown>[];
imap?: Record<string, unknown>[];
postbox?: Record<string, unknown>[];
print?: Record<string, unknown>[];
};
};
export type CampaignReportEmailPayload = {
to: string[];
version_id?: string;
include_jobs?: boolean;
attach_jobs_csv?: boolean;
attach_report_json?: boolean;
dry_run?: boolean;
idempotency_key?: string;
};
export type AggregateReportCount = {
value: number | null;
suppressed: boolean;
};
export type AggregateReportCampaign = {
id: string;
name: string;
status: string;
};
export type AggregateReportCampaignListItem = AggregateReportCampaign & {
updated_at: string;
};
export type AggregateCampaignReport = {
generated_at: string;
campaign: AggregateReportCampaign;
version_number: number | null;
completion_state: "not_started" | "in_progress" | "completed" | "partially_completed" | "incomplete" | "outcome_unknown" | "suppressed";
population: {
denominator: AggregateReportCount;
denominator_definition: string;
inactive_source_entries: AggregateReportCount;
excluded_or_blocked_jobs: AggregateReportCount;
};
outcomes: {
smtp_accepted: AggregateReportCount;
postbox_accepted: AggregateReportCount;
print_accepted: AggregateReportCount;
delivered: AggregateReportCount;
partially_accepted: AggregateReportCount;
failed: AggregateReportCount;
outcome_unknown: AggregateReportCount;
queued_or_active: AggregateReportCount;
cancelled: AggregateReportCount;
excluded: AggregateReportCount;
not_attempted: AggregateReportCount;
};
time_range: {
first_activity_at: string | null;
last_activity_at: string | null;
suppressed: boolean;
};
privacy: {
small_cell_threshold: number;
suppression_applied: boolean;
rule: string;
};
};
export async function listCampaigns(settings: ApiSettings): Promise<CampaignListItem[]> {
const response = await apiFetch<CampaignListResponse>(settings, "/api/v1/campaigns");
if (Array.isArray(response)) {
return response;
}
return response.campaigns ?? response.items ?? response.results ?? [];
}
export async function listAggregateReportCampaigns(
settings: ApiSettings)
: Promise<AggregateReportCampaignListItem[]> {
const response = await apiFetch<{campaigns: AggregateReportCampaignListItem[]}>(settings, "/api/v1/campaigns/aggregate-reports");
return response.campaigns;
}
export async function listCampaignsDelta(
settings: ApiSettings,
options: {since?: string | null;limit?: number;} = {})
: Promise<CampaignDeltaResponse> {
const params = new URLSearchParams();
if (options.since) params.set("since", options.since);
if (options.limit) params.set("limit", String(options.limit));
const suffix = params.size > 0 ? `?${params.toString()}` : "";
return apiFetch<CampaignDeltaResponse>(settings, `/api/v1/campaigns/delta${suffix}`);
}
export async function listRecipientImportMappingProfiles(settings: ApiSettings): Promise<RecipientImportMappingProfile[]> {
const response = await apiFetch<RecipientImportMappingProfileListResponse>(settings, "/api/v1/campaigns/recipient-import/mapping-profiles");
return response.profiles ?? [];
}
export async function createRecipientImportMappingProfile(
settings: ApiSettings,
payload: RecipientImportMappingProfilePayload)
: Promise<RecipientImportMappingProfile> {
return apiFetch<RecipientImportMappingProfile>(settings, "/api/v1/campaigns/recipient-import/mapping-profiles", {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function updateRecipientImportMappingProfile(
settings: ApiSettings,
profileId: string,
payload: RecipientImportMappingProfilePayload)
: Promise<RecipientImportMappingProfile> {
return apiFetch<RecipientImportMappingProfile>(settings, `/api/v1/campaigns/recipient-import/mapping-profiles/${encodeURIComponent(profileId)}`, {
method: "PUT",
body: JSON.stringify(payload)
});
}
export async function deleteRecipientImportMappingProfile(settings: ApiSettings, profileId: string): Promise<void> {
await apiFetch<void>(settings, `/api/v1/campaigns/recipient-import/mapping-profiles/${encodeURIComponent(profileId)}`, { method: "DELETE" });
}
export async function lookupCampaignAddresses(
settings: ApiSettings,
campaignId: string,
query: string,
limit = 25)
: Promise<CampaignAddressLookupResponse> {
const params = new URLSearchParams({ query, limit: String(limit) });
return apiFetch<CampaignAddressLookupResponse>(settings, `/api/v1/campaigns/${campaignId}/address-lookup?${params.toString()}`);
}
export async function listCampaignRecipientAddressSources(
settings: ApiSettings,
campaignId: string)
: Promise<CampaignRecipientAddressSourcesResponse> {
return apiFetch<CampaignRecipientAddressSourcesResponse>(settings, `/api/v1/campaigns/${campaignId}/recipient-address-sources`);
}
export async function listCampaignRecipientDistributionLists(
settings: ApiSettings,
campaignId: string,
query = "")
: Promise<CampaignDistributionListSourcesResponse> {
const params = new URLSearchParams();
params.set("limit", "250");
if (query.trim()) params.set("query", query.trim());
const suffix = params.size ? `?${params.toString()}` : "";
return apiFetch<CampaignDistributionListSourcesResponse>(settings, `/api/v1/campaigns/${campaignId}/recipient-distribution-lists${suffix}`);
}
export async function previewCampaignRecipientDistributionList(
settings: ApiSettings,
campaignId: string,
payload: CampaignDistributionListExpansionInput)
: Promise<CampaignDistributionListExpansion> {
return apiFetch<CampaignDistributionListExpansion>(settings, `/api/v1/campaigns/${campaignId}/recipient-distribution-lists/preview`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function snapshotCampaignRecipientDistributionList(
settings: ApiSettings,
campaignId: string,
payload: CampaignDistributionListExpansionInput)
: Promise<CampaignDistributionListExpansion> {
return apiFetch<CampaignDistributionListExpansion>(settings, `/api/v1/campaigns/${campaignId}/recipient-distribution-lists/snapshot`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function getCampaignPostboxCatalog(
settings: ApiSettings,
campaignId: string)
: Promise<CampaignPostboxCatalog> {
return apiFetch<CampaignPostboxCatalog>(settings, `/api/v1/campaigns/${campaignId}/postbox-catalog`);
}
export async function getCampaignCalendarInvitationCatalog(
settings: ApiSettings,
campaignId: string)
: Promise<CampaignCalendarCatalog> {
return apiFetch<CampaignCalendarCatalog>(settings, `/api/v1/campaigns/${campaignId}/calendar-invitation-catalog`);
}
export async function listCampaignPrintTemplates(
settings: ApiSettings,
campaignId: string,
query = "")
: Promise<CampaignPrintTemplatesResponse> {
const suffix = query.trim() ? `?query=${encodeURIComponent(query.trim())}` : "";
return apiFetch<CampaignPrintTemplatesResponse>(settings, `/api/v1/campaigns/${campaignId}/print-templates${suffix}`);
}
export async function listCampaignContentLibrary(
settings: ApiSettings,
campaignId: string,
query = "")
: Promise<CampaignContentLibraryResponse> {
const suffix = query.trim() ? `?query=${encodeURIComponent(query.trim())}` : "";
return apiFetch<CampaignContentLibraryResponse>(settings, `/api/v1/campaigns/${campaignId}/content-library${suffix}`);
}
export async function saveCampaignContentLibraryItem(
settings: ApiSettings,
campaignId: string,
payload: CampaignContentLibrarySaveInput)
: Promise<{template: CampaignContentLibraryItem;}> {
return apiFetch<{template: CampaignContentLibraryItem;}>(settings, `/api/v1/campaigns/${campaignId}/content-library`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function snapshotCampaignRecipientAddressSource(
settings: ApiSettings,
campaignId: string,
sourceId: string)
: Promise<CampaignRecipientAddressSourceSnapshot> {
return apiFetch<CampaignRecipientAddressSourceSnapshot>(settings, `/api/v1/campaigns/${campaignId}/recipient-address-sources/snapshot`, {
method: "POST",
body: JSON.stringify({ source_id: sourceId, purpose: "campaign_delivery" })
});
}
export async function getCampaign(settings: ApiSettings, campaignId: string): Promise<CampaignListItem> {
return apiFetch<CampaignListItem>(settings, `/api/v1/campaigns/${campaignId}`);
}
export async function getCampaignArchiveEncryptionPolicy(
settings: ApiSettings,
campaignId: string
): Promise<CampaignArchiveEncryptionPolicy> {
return apiFetch<CampaignArchiveEncryptionPolicy>(
settings,
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/archive-encryption-policy`
);
}
export async function updateCampaignMetadata(
settings: ApiSettings,
campaignId: string,
payload: CampaignUpdatePayload)
: Promise<CampaignListItem> {
return apiFetch<CampaignListItem>(settings, `/api/v1/campaigns/${campaignId}`, {
method: "PUT",
body: JSON.stringify(payload)
});
}
export async function archiveCampaign(
settings: ApiSettings,
campaignId: string,
expectedStateToken: string)
: Promise<CampaignListItem> {
return apiFetch<CampaignListItem>(settings, `/api/v1/campaigns/${campaignId}/archive`, {
method: "POST",
body: JSON.stringify({ expected_state_token: expectedStateToken })
});
}
export async function getCampaignLifecyclePolicy(
settings: ApiSettings,
campaignId: string,
versionId?: string | null)
: Promise<CampaignLifecyclePolicy> {
const suffix = versionId ? `?version_id=${encodeURIComponent(versionId)}` : "";
return apiFetch<CampaignLifecyclePolicy>(settings, `/api/v1/campaigns/${campaignId}/lifecycle-policy${suffix}`);
}
export async function deleteCampaign(
settings: ApiSettings,
campaignId: string,
expectedStateToken: string)
: Promise<void> {
await apiFetch<void>(settings, `/api/v1/campaigns/${campaignId}`, {
method: "DELETE",
body: JSON.stringify({ expected_state_token: expectedStateToken })
});
}
export async function copyCampaign(
settings: ApiSettings,
campaignId: string,
sourceVersionId: string,
expectedStateToken: string,
options: CampaignCopyOptions)
: Promise<CampaignCreateResponse> {
return apiFetch<CampaignCreateResponse>(settings, `/api/v1/campaigns/${campaignId}/copies`, {
method: "POST",
body: JSON.stringify({
source_version_id: sourceVersionId,
expected_state_token: expectedStateToken,
...options,
name: options.name?.trim() || undefined,
external_id: options.external_id?.trim() || undefined
})
});
}
export async function exportCampaignPackage(
settings: ApiSettings,
campaignId: string,
versionId: string,
scopes: CampaignTransferScope[])
: Promise<CampaignPortablePackage> {
return apiFetch<CampaignPortablePackage>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/exports`, {
method: "POST",
body: JSON.stringify({ scopes })
});
}
export async function previewCampaignImport(
settings: ApiSettings,
payload: {
package: Record<string, unknown>;
selected_scopes?: CampaignTransferScope[] | null;
external_id?: string;
name?: string;
})
: Promise<CampaignImportPreview> {
return apiFetch<CampaignImportPreview>(settings, "/api/v1/campaign-transfers/imports/preview", {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function importCampaignPackage(
settings: ApiSettings,
payload: {
package: Record<string, unknown>;
selected_scopes: CampaignTransferScope[];
external_id?: string;
name?: string;
expected_package_sha256: string;
})
: Promise<CampaignImportApplyResponse> {
return apiFetch<CampaignImportApplyResponse>(settings, "/api/v1/campaign-transfers/imports", {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function listCampaignSchedules(
settings: ApiSettings,
campaignId: string)
: Promise<CampaignSchedule[]> {
const response = await apiFetch<{items: CampaignSchedule[]}>(settings, `/api/v1/campaigns/${campaignId}/schedules`);
return response.items;
}
export async function createCampaignSchedule(
settings: ApiSettings,
campaignId: string,
payload: CampaignScheduleCreate)
: Promise<CampaignSchedule> {
return apiFetch<CampaignSchedule>(settings, `/api/v1/campaigns/${campaignId}/schedules`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function setCampaignScheduleState(
settings: ApiSettings,
campaignId: string,
scheduleId: string,
active: boolean,
baseRevision: number)
: Promise<CampaignSchedule> {
return apiFetch<CampaignSchedule>(settings, `/api/v1/campaigns/${campaignId}/schedules/${scheduleId}`, {
method: "PATCH",
body: JSON.stringify({ active, base_revision: baseRevision })
});
}
export async function archiveCampaignVersion(
settings: ApiSettings,
campaignId: string,
versionId: string,
expectedStateToken: string)
: Promise<CampaignVersionListItem> {
return apiFetch<CampaignVersionListItem>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/archive`, {
method: "POST",
body: JSON.stringify({ expected_state_token: expectedStateToken })
});
}
export async function createNewCampaign(
settings: ApiSettings,
overrides: CampaignCreateMinimalPayload = {})
: Promise<CampaignCreateResponse> {
const now = new Date();
const stamp = now.toISOString().slice(0, 19).replace(/[-:T]/g, "");
const payload = {
external_id: overrides.external_id ?? `new-campaign-${stamp}`,
name: overrides.name ?? "i18n:govoplan-campaign.new_campaign.1f0d021c",
description: overrides.description ?? "",
current_flow: overrides.current_flow ?? "create",
current_step: overrides.current_step ?? "basics"
};
return apiFetch<CampaignCreateResponse>(settings, "/api/v1/campaigns/new", {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function getCampaignSchema(settings: ApiSettings): Promise<unknown> {
return apiFetch(settings, "/api/v1/schemas/campaign");
}
export async function getCampaignSchemaUi(settings: ApiSettings): Promise<unknown> {
return apiFetch(settings, "/api/v1/schemas/campaign/ui");
}
export async function getCampaignWorkspace(
settings: ApiSettings,
campaignId: string,
options: CampaignWorkspaceQuery = {})
: Promise<CampaignWorkspaceResponse> {
const params = new URLSearchParams();
if (options.versionId) params.set("version_id", options.versionId);
if (options.includeCurrentVersion !== undefined) params.set("include_current_version", String(options.includeCurrentVersion));
if (options.includeSummary !== undefined) params.set("include_summary", String(options.includeSummary));
if (options.includeVersions !== undefined) params.set("include_versions", String(options.includeVersions));
const suffix = params.size > 0 ? `?${params.toString()}` : "";
return apiFetch<CampaignWorkspaceResponse>(settings, `/api/v1/campaigns/${campaignId}/workspace${suffix}`);
}
export async function getCampaignWorkspaceDelta(
settings: ApiSettings,
campaignId: string,
options: CampaignWorkspaceQuery = {},
requestOptions?: Pick<RequestInit, "cache" | "signal">)
: Promise<CampaignWorkspaceDeltaResponse> {
const params = new URLSearchParams();
if (options.versionId) params.set("version_id", options.versionId);
if (options.includeCurrentVersion !== undefined) params.set("include_current_version", String(options.includeCurrentVersion));
if (options.includeSummary !== undefined) params.set("include_summary", String(options.includeSummary));
if (options.includeVersions !== undefined) params.set("include_versions", String(options.includeVersions));
if (options.since) params.set("since", options.since);
if (options.limit) params.set("limit", String(options.limit));
const suffix = params.size > 0 ? `?${params.toString()}` : "";
return apiFetch<CampaignWorkspaceDeltaResponse>(settings, `/api/v1/campaigns/${campaignId}/workspace/delta${suffix}`, requestOptions);
}
export async function listCampaignVersions(
settings: ApiSettings,
campaignId: string)
: Promise<CampaignVersionListItem[]> {
return apiFetch<CampaignVersionListItem[]>(settings, `/api/v1/campaigns/${campaignId}/versions`);
}
export async function getCampaignVersion(
settings: ApiSettings,
campaignId: string,
versionId: string)
: Promise<CampaignVersionDetail> {
// Conflict reconciliation and discard/reload require the current revision,
// never an older coalesced or short-lived cached read.
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}`, { cache: "no-store" });
}
export async function unlockCampaignVersionValidation(
settings: ApiSettings,
campaignId: string,
versionId: string)
: Promise<CampaignVersionDetail> {
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/unlock-validation`, {
method: "POST"
});
}
export async function lockCampaignVersionTemporarily(
settings: ApiSettings,
campaignId: string,
versionId: string)
: Promise<CampaignVersionDetail> {
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/lock-temporarily`, {
method: "POST"
});
}
export async function unlockCampaignVersionUserLock(
settings: ApiSettings,
campaignId: string,
versionId: string)
: Promise<CampaignVersionDetail> {
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/unlock-user-lock`, {
method: "POST"
});
}
export async function lockCampaignVersionPermanently(
settings: ApiSettings,
campaignId: string,
versionId: string)
: Promise<CampaignVersionDetail> {
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/lock-permanently`, {
method: "POST"
});
}
export async function updateCampaignVersion(
settings: ApiSettings,
campaignId: string,
versionId: string,
payload: CampaignVersionUpdatePayload,
ifMatch: string)
: Promise<CampaignVersionDetail> {
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}`, {
method: "PUT",
headers: { "If-Match": ifMatch },
body: JSON.stringify(campaignVersionUpdateForRequest(payload))
});
}
export async function forkCampaignVersion(
settings: ApiSettings,
campaignId: string,
versionId: string,
payload: CampaignVersionUpdatePayload = {})
: Promise<CampaignCreateResponse> {
return apiFetch<CampaignCreateResponse>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/fork`, {
method: "POST",
body: JSON.stringify(campaignVersionUpdateForRequest(payload))
});
}
export async function autosaveCampaignVersion(
settings: ApiSettings,
campaignId: string,
versionId: string,
payload: CampaignVersionUpdatePayload,
ifMatch: string)
: Promise<CampaignVersionDetail> {
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/autosave`, {
method: "POST",
headers: { "If-Match": ifMatch },
body: JSON.stringify(campaignVersionUpdateForRequest(payload))
});
}
export async function setCampaignVersionStep(
settings: ApiSettings,
campaignId: string,
versionId: string,
currentStep: string,
currentFlow?: string | null)
: Promise<CampaignVersionDetail> {
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/set-step`, {
method: "POST",
body: JSON.stringify({ current_flow: currentFlow, current_step: currentStep })
});
}
export async function validatePartial(
settings: ApiSettings,
campaignId: string,
versionId: string,
payload: CampaignPartialValidationPayload = {})
: Promise<CampaignPartialValidationResponse> {
return apiFetch<CampaignPartialValidationResponse>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/validate-partial`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function publishCampaignVersion(
settings: ApiSettings,
campaignId: string,
versionId: string)
: Promise<CampaignVersionDetail> {
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/publish`, {
method: "POST"
});
}
export async function validateVersion(
settings: ApiSettings,
versionId: string,
checkFiles = false,
linkUnsharedMatches = false)
: Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/versions/${versionId}/validate`, {
method: "POST",
body: JSON.stringify({ check_files: checkFiles, link_unshared_matches: linkUnsharedMatches })
});
}
export async function buildVersion(
settings: ApiSettings,
versionId: string,
writeEml = true)
: Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/versions/${versionId}/build`, {
method: "POST",
body: JSON.stringify({ write_eml: writeEml, idempotency_key: crypto.randomUUID() })
});
}
export function previewCampaignAttachments(
settings: ApiSettings,
campaignId: string,
versionId: string,
payload: CampaignAttachmentPreviewPayload = {})
: Promise<CampaignAttachmentPreviewResponse> {
return apiFetch<CampaignAttachmentPreviewResponse>(
settings,
`/api/v1/campaigns/${campaignId}/versions/${versionId}/attachments/preview`,
{ method: "POST", body: JSON.stringify(payload) }
);
}
export function linkCampaignAttachmentMatches(
settings: ApiSettings,
campaignId: string,
versionId: string,
payload: CampaignAttachmentLinkMatchesPayload = {})
: Promise<CampaignAttachmentLinkMatchesResponse> {
return apiFetch<CampaignAttachmentLinkMatchesResponse>(
settings,
`/api/v1/campaigns/${campaignId}/versions/${versionId}/attachments/link-matches`,
{ method: "POST", body: JSON.stringify(payload) }
);
}
export async function getCampaignSummary(
settings: ApiSettings,
campaignId: string,
versionId?: string)
: Promise<CampaignSummary> {
const suffix = versionId ? `?version_id=${encodeURIComponent(versionId)}` : "";
return apiFetch<CampaignSummary>(settings, `/api/v1/campaigns/${campaignId}/summary${suffix}`);
}
export type CampaignDeliveryProgress = {
delivery_mode?: string | null;
workflow_state?: string;
campaign_id: string;
version_id: string;
generated_at: string;
total_jobs: number;
smtp: { total: number; processed: number; accepted: number; active: number; pending: number; failed: number; outcome_unknown: number; excluded: number; paused: number; cancelled: number };
imap: { total: number; processed: number; appended: number; active: number; pending: number; failed: number; outcome_unknown: number; excluded: number };
status_counts: { send: Record<string, number>; queue: Record<string, number>; imap: Record<string, number> };
};
export async function getCampaignDeliveryProgress(
settings: ApiSettings, campaignId: string, versionId: string, signal?: AbortSignal
): Promise<CampaignDeliveryProgress> {
return apiFetch<CampaignDeliveryProgress>(settings,
`/api/v1/campaigns/${campaignId}/delivery-progress?version_id=${encodeURIComponent(versionId)}`,
{ cache: "no-store", signal });
}
export async function getCampaignJobs(
settings: ApiSettings,
campaignId: string,
options: CampaignJobsQuery = {},
init?: Pick<RequestInit, "cache" | "signal">)
: Promise<CampaignJobsResponse> {
const params = campaignJobsQueryParams(options);
const suffix = params.size > 0 ? `?${params.toString()}` : "";
return apiFetch<CampaignJobsResponse>(settings, `/api/v1/campaigns/${campaignId}/jobs${suffix}`, init);
}
export async function getCampaignJobsDelta(
settings: ApiSettings,
campaignId: string,
options: CampaignJobsQuery & {since?: string | null;limit?: number;} = {},
init?: Pick<RequestInit, "cache" | "signal">)
: Promise<CampaignJobsDeltaResponse> {
const params = campaignJobsQueryParams(options);
const suffix = params.size > 0 ? `?${params.toString()}` : "";
return apiFetch<CampaignJobsDeltaResponse>(settings, `/api/v1/campaigns/${campaignId}/jobs/delta${suffix}`, init);
}
export async function getCampaignJobDetail(
settings: ApiSettings,
campaignId: string,
jobId: string)
: Promise<CampaignJobDetailResponse> {
return apiFetch<CampaignJobDetailResponse>(settings, `/api/v1/campaigns/${campaignId}/jobs/${jobId}`);
}
export async function getCampaignReport(
settings: ApiSettings,
campaignId: string,
versionId?: string)
: Promise<CampaignSummary> {
const params = new URLSearchParams({ include_jobs: "false" });
if (versionId) params.set("version_id", versionId);
return apiFetch<CampaignSummary>(settings, `/api/v1/campaigns/${campaignId}/report?${params.toString()}`);
}
export async function getAggregateCampaignReport(
settings: ApiSettings,
campaignId: string)
: Promise<AggregateCampaignReport> {
return apiFetch<AggregateCampaignReport>(
settings,
`/api/v1/campaigns/aggregate-reports/${encodeURIComponent(campaignId)}`
);
}
export async function downloadCampaignJobsCsv(
settings: ApiSettings,
campaignId: string,
versionId?: string)
: Promise<void> {
const suffix = versionId ? `?version_id=${encodeURIComponent(versionId)}` : "";
await apiDownload(
settings,
`/api/v1/campaigns/${campaignId}/report/jobs.csv${suffix}`,
`campaign-${campaignId}${versionId ? `-${versionId}` : ""}-jobs.csv`
);
}
export async function downloadCampaignPrintArtifact(
settings: ApiSettings,
downloadPath: string,
filename: string)
: Promise<void> {
if (![
"/api/v1/campaigns/",
"/api/v1/files/"
].some((prefix) => downloadPath.startsWith(prefix))) {
throw new Error("The printable output download path is invalid.");
}
await apiDownload(settings, downloadPath, filename || "campaign-print-output.html");
}
export async function emailCampaignReport(
settings: ApiSettings,
campaignId: string,
payload: CampaignReportEmailPayload)
: Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/${campaignId}/report/email`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function retryCampaignJobs(
settings: ApiSettings,
campaignId: string,
payload: Record<string, unknown> = {})
: Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/${campaignId}/jobs/retry`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function sendUnattemptedCampaignJobs(
settings: ApiSettings,
campaignId: string,
payload: Record<string, unknown> = {})
: Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/${campaignId}/jobs/send-unattempted`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function sendCampaignJob(
settings: ApiSettings,
campaignId: string,
jobId: string,
payload: CampaignSendJobPayload)
: Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/${campaignId}/jobs/${jobId}/send`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function resolveCampaignJobOutcome(
settings: ApiSettings,
campaignId: string,
jobId: string,
decision: "smtp_accepted" | "not_sent" | "imap_appended" | "imap_not_appended" | "postbox_accepted" | "postbox_not_accepted",
note?: string,
attemptId?: string)
: Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/${campaignId}/jobs/${jobId}/resolve-outcome`, {
method: "POST",
body: JSON.stringify({ decision, note: note || null, attempt_id: attemptId || null })
});
}
export async function updateCampaignReviewState(
settings: ApiSettings,
campaignId: string,
versionId: string,
payload: CampaignReviewStatePayload)
: Promise<CampaignVersionDetail> {
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/review-state`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function queueCampaign(
settings: ApiSettings,
campaignId: string,
payload: CampaignQueuePayload = {})
: Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/${campaignId}/queue`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function getCampaignDeliveryOptions(
settings: ApiSettings,
campaignId: string,
versionId?: string | null)
: Promise<CampaignDeliveryOptions> {
const query = versionId ? `?version_id=${encodeURIComponent(versionId)}` : "";
return apiFetch<CampaignDeliveryOptions>(settings, `/api/v1/campaigns/${campaignId}/delivery-options${query}`);
}
export async function requestCampaignApproval(
settings: ApiSettings,
campaignId: string,
versionId: string,
payload: CampaignApprovalRequestPayload)
: Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(
settings,
`/api/v1/campaigns/${campaignId}/versions/${versionId}/approval-request`,
{ method: "POST", body: JSON.stringify(payload) }
);
}
export async function sendCampaignNow(
settings: ApiSettings,
campaignId: string,
payload: CampaignSendNowPayload = {})
: Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/${campaignId}/send-now`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function mockSendCampaign(
settings: ApiSettings,
campaignId: string,
payload: CampaignMockSendPayload = {})
: Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/${campaignId}/mock-send`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function pauseCampaign(settings: ApiSettings, campaignId: string): Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/${campaignId}/pause`, { method: "POST" });
}
export async function resumeCampaign(settings: ApiSettings, campaignId: string): Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/${campaignId}/resume`, { method: "POST" });
}
export async function cancelCampaign(settings: ApiSettings, campaignId: string): Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/${campaignId}/cancel`, { method: "POST" });
}
export async function appendSent(
settings: ApiSettings,
campaignId: string,
payload: CampaignAppendSentPayload = {})
: Promise<Record<string, unknown>> {
return apiFetch<Record<string, unknown>>(settings, `/api/v1/campaigns/${campaignId}/append-sent`, {
method: "POST",
body: JSON.stringify({
version_id: payload.version_id,
dry_run: payload.dry_run ?? false,
enqueue_celery: payload.enqueue_celery ?? true,
run_inline: payload.run_inline ?? false
})
});
}
export async function getCampaignShareTargets(settings: ApiSettings, campaignId: string): Promise<CampaignShareTargets> {
return apiFetch<CampaignShareTargets>(settings, `/api/v1/campaigns/${campaignId}/share-targets`);
}
export async function listCampaignCollaboration(
settings: ApiSettings,
campaignId: string,
options: {cursor?: string | null;limit?: number;} = {}
): Promise<CampaignCollaborationListResponse> {
const params = new URLSearchParams({ limit: String(options.limit ?? 25) });
if (options.cursor) params.set("cursor", options.cursor);
return apiFetch<CampaignCollaborationListResponse>(
settings,
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/collaboration?${params.toString()}`
);
}
export async function createCampaignCollaborationEntry(
settings: ApiSettings,
campaignId: string,
payload: CampaignCollaborationCreate
): Promise<CampaignCollaborationEntry> {
return apiFetch<CampaignCollaborationEntry>(
settings,
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/collaboration`,
{ method: "POST", body: JSON.stringify(payload) }
);
}
export async function withdrawCampaignCollaborationEntry(
settings: ApiSettings,
campaignId: string,
entryId: string
): Promise<CampaignCollaborationEntry> {
return apiFetch<CampaignCollaborationEntry>(
settings,
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/collaboration/${encodeURIComponent(entryId)}/withdraw`,
{ method: "POST", body: JSON.stringify({}) }
);
}
export async function redactCampaignCollaborationEntry(
settings: ApiSettings,
campaignId: string,
entryId: string
): Promise<CampaignCollaborationEntry> {
return apiFetch<CampaignCollaborationEntry>(
settings,
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/collaboration/${encodeURIComponent(entryId)}/redact`,
{ method: "POST", body: JSON.stringify({}) }
);
}
export function campaignCollaborationMentionProvider(
settings: ApiSettings,
campaignId: string
): ReferenceOptionProvider {
return apiReferenceOptionProvider(
settings,
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/collaboration/mention-options`
);
}
export async function listCampaignWorkAssignments(
settings: ApiSettings,
campaignId: string,
options: { cursor?: string | null; limit?: number; statuses?: CampaignWorkAssignmentStatus[] } = {}
): Promise<CampaignWorkAssignmentListResponse> {
const params = new URLSearchParams({ limit: String(options.limit ?? 50) });
if (options.cursor) params.set("cursor", options.cursor);
for (const status of options.statuses ?? []) params.append("assignment_status", status);
return apiFetch<CampaignWorkAssignmentListResponse>(
settings,
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments?${params.toString()}`
);
}
export async function createCampaignWorkAssignment(
settings: ApiSettings,
campaignId: string,
payload: {
purpose: string;
assignee: { type: CampaignWorkAssigneeType; id: string };
due_at?: string | null;
reference?: Pick<CampaignCollaborationReference, "kind" | "id"> | null;
mirror_to_tasks?: boolean;
}
): Promise<CampaignWorkAssignment> {
return apiFetch<CampaignWorkAssignment>(
settings,
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments`,
{ method: "POST", body: JSON.stringify(payload) }
);
}
export async function reassignCampaignWorkAssignment(
settings: ApiSettings,
campaignId: string,
assignmentId: string,
payload: {
expected_revision: number;
assignee: { type: CampaignWorkAssigneeType; id: string };
reason?: string | null;
mirror_to_tasks?: boolean;
}
): Promise<CampaignWorkAssignment> {
return apiFetch<CampaignWorkAssignment>(
settings,
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments/${encodeURIComponent(assignmentId)}/reassign`,
{ method: "POST", body: JSON.stringify(payload) }
);
}
export async function transitionCampaignWorkAssignment(
settings: ApiSettings,
campaignId: string,
assignment: Pick<CampaignWorkAssignment, "id" | "resource_revision">,
action: "accept" | "start" | "complete" | "reject" | "cancel"
): Promise<CampaignWorkAssignment> {
return apiFetch<CampaignWorkAssignment>(
settings,
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments/${encodeURIComponent(assignment.id)}/transition`,
{ method: "POST", body: JSON.stringify({ expected_revision: assignment.resource_revision, action }) }
);
}
export async function listCampaignWorkAssignmentHistory(
settings: ApiSettings,
campaignId: string,
assignmentId: string,
cursor?: string | null
): Promise<CampaignWorkAssignmentHistoryResponse> {
const params = new URLSearchParams({ limit: "50" });
if (cursor) params.set("cursor", cursor);
return apiFetch<CampaignWorkAssignmentHistoryResponse>(
settings,
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments/${encodeURIComponent(assignmentId)}/history?${params.toString()}`
);
}
export async function reconcileCampaignWorkAssignments(
settings: ApiSettings,
campaignId: string
): Promise<{ checked: number; changed: number; assignments: CampaignWorkAssignment[] }> {
return apiFetch(
settings,
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments/reconcile`,
{ method: "POST" }
);
}
export function campaignWorkAssignmentTargetProvider(
settings: ApiSettings,
campaignId: string,
assigneeType: CampaignWorkAssigneeType
): ReferenceOptionProvider {
return apiReferenceOptionProvider(
settings,
`/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments/options`,
{ assignee_type: assigneeType }
);
}
export function campaignShareTargetProvider(
settings: ApiSettings,
campaignId: string,
targetType: "user" | "group"
): ReferenceOptionProvider {
return apiReferenceOptionProvider(
settings,
`/api/v1/campaigns/${campaignId}/share-target-options`,
{ target_type: targetType }
);
}
export async function getCampaignShares(settings: ApiSettings, campaignId: string): Promise<CampaignShare[]> {
const pageSize = 500;
const shares: CampaignShare[] = [];
for (let page = 1; ; page += 1) {
const response = await apiFetch<{shares: CampaignShare[];pages?: number;}>(settings, `/api/v1/campaigns/${campaignId}/shares?page=${page}&page_size=${pageSize}`);
shares.push(...response.shares);
if (page >= (response.pages ?? 1)) {
return shares;
}
}
}
export async function updateCampaignOwner(
settings: ApiSettings,
campaignId: string,
payload: {owner_user_id?: string | null;owner_group_id?: string | null;})
: Promise<CampaignListItem> {
return apiFetch<CampaignListItem>(settings, `/api/v1/campaigns/${campaignId}/owner`, { method: "PUT", body: JSON.stringify(payload) });
}
export async function upsertCampaignShare(
settings: ApiSettings,
campaignId: string,
payload: {target_type: "user" | "group";target_id: string;permission: "read" | "write";})
: Promise<CampaignShare> {
return apiFetch<CampaignShare>(settings, `/api/v1/campaigns/${campaignId}/shares`, { method: "POST", body: JSON.stringify(payload) });
}
export async function revokeCampaignShare(settings: ApiSettings, campaignId: string, shareId: string): Promise<void> {
await apiFetch<void>(settings, `/api/v1/campaigns/${campaignId}/shares/${shareId}`, { method: "DELETE" });
}