Files
govoplan-campaign/webui/src/api/campaigns.ts

1010 lines
31 KiB
TypeScript

import type { ApiSettings, CampaignListItem, DeltaDeletedItem } from "../types";
import { apiDownload, apiFetch } from "./client";
import { campaignJobsQueryParams, type CampaignJobsQueryParameters } from "../features/campaigns/utils/jobListQuery";
export { fetchResourceAccessExplanation } from "@govoplan/core-webui";
export type {
AccessDecisionProvenanceItem,
ResourceAccessExplanationUser as AccessExplanationUser,
ResourceAccessExplanationResponse
} 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 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;
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;
};
export type CampaignVersionDetail = CampaignVersionListItem & {
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;
};
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 CampaignRecipientSnapshotItem = {
contact_id: string;
display_name: string;
email: string;
email_label?: string | null;
fields: Record<string, unknown>;
provenance: Record<string, unknown>;
};
export type CampaignRecipientAddressSourceSnapshot = {
source_id: string;
source_label: string;
source_kind: string;
source_revision: string;
generated_at: string;
recipients: CampaignRecipientSnapshotItem[];
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;
};
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 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;
} | null;
cards?: {
jobs_total?: number;
inactive?: number;
queueable?: number;
needs_attention?: number;
sent?: number;
smtp_accepted?: number;
failed?: number;
outcome_unknown?: number;
not_attempted?: number;
queued_or_active?: number;
cancelled?: number;
partially_completed?: boolean;
imap_appended?: number;
imap_failed?: number;
};
status_counts?: Record<string, Record<string, number>>;
issues?: Record<string, unknown>;
attachments?: Record<string, unknown>;
attempts?: Record<string, unknown>;
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;
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 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 = {
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 = {
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 = {
inspection_complete: boolean;
reviewed_message_keys: string[];
};
export type CampaignSendJobPayload = {
include_warnings?: boolean;
dry_run?: 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>[];
};
};
export type CampaignReportEmailPayload = {
to: string[];
version_id?: string;
include_jobs?: boolean;
attach_jobs_csv?: boolean;
attach_report_json?: boolean;
dry_run?: boolean;
};
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 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 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 })
});
}
export async function getCampaign(settings: ApiSettings, campaignId: string): Promise<CampaignListItem> {
return apiFetch<CampaignListItem>(settings, `/api/v1/campaigns/${campaignId}`);
}
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 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 = {})
: 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}`);
}
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> {
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}`);
}
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)
: Promise<CampaignVersionDetail> {
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}`, {
method: "PUT",
body: JSON.stringify(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(payload)
});
}
export async function autosaveCampaignVersion(
settings: ApiSettings,
campaignId: string,
versionId: string,
payload: CampaignVersionUpdatePayload)
: Promise<CampaignVersionDetail> {
return apiFetch<CampaignVersionDetail>(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/autosave`, {
method: "POST",
body: JSON.stringify(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 })
});
}
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 async function getCampaignJobs(
settings: ApiSettings,
campaignId: string,
options: CampaignJobsQuery = {})
: Promise<CampaignJobsResponse> {
const params = campaignJobsQueryParams(options);
const suffix = params.size > 0 ? `?${params.toString()}` : "";
return apiFetch<CampaignJobsResponse>(settings, `/api/v1/campaigns/${campaignId}/jobs${suffix}`);
}
export async function getCampaignJobsDelta(
settings: ApiSettings,
campaignId: string,
options: CampaignJobsQuery & {since?: string | null;limit?: number;} = {})
: Promise<CampaignJobsDeltaResponse> {
const params = campaignJobsQueryParams(options);
const suffix = params.size > 0 ? `?${params.toString()}` : "";
return apiFetch<CampaignJobsDeltaResponse>(settings, `/api/v1/campaigns/${campaignId}/jobs/delta${suffix}`);
}
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 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 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",
note?: 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 })
});
}
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 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({
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 getCampaignShares(settings: ApiSettings, campaignId: string): Promise<CampaignShare[]> {
const response = await apiFetch<{shares: CampaignShare[];}>(settings, `/api/v1/campaigns/${campaignId}/shares`);
return response.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" });
}