{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://govoplan.local/schema/campaign.schema.json", "title": "GovOPlaN Campaign", "type": "object", "required": [ "version", "campaign", "template", "entries" ], "properties": { "version": { "type": "string", "const": "1.0" }, "campaign": { "type": "object", "required": [ "id", "name" ], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "description": { "type": "string" }, "mode": { "type": "string", "enum": [ "draft", "test", "send" ], "default": "draft" } }, "additionalProperties": false }, "fields": { "type": "array", "items": { "type": "object", "required": [ "name" ], "properties": { "name": { "type": "string" }, "type": { "type": "string", "enum": [ "string", "integer", "double", "date", "password", "organization_unit", "organization_function" ], "default": "string" }, "label": { "type": "string" }, "required": { "type": "boolean", "default": false }, "can_override": { "type": "boolean", "default": true, "description": "Whether recipient/entry field values may override the global value for this field." } }, "additionalProperties": false }, "default": [] }, "global_values": { "type": "object", "additionalProperties": true, "default": {} }, "server": { "type": "object", "properties": { "mail_profile_id": { "type": "string", "minLength": 1, "description": "Stable reference to an authorized server envelope owned by the Mail module. Campaign JSON never stores SMTP/IMAP settings or credentials." }, "smtp_server_id": { "type": "string", "minLength": 1, "description": "Optional explicit Mail-owned SMTP server selection." }, "smtp_credential_id": { "type": "string", "minLength": 1, "description": "Optional explicit core credential envelope bound to the selected SMTP server." }, "imap_server_id": { "type": "string", "minLength": 1, "description": "Optional explicit Mail-owned IMAP server selection." }, "imap_credential_id": { "type": "string", "minLength": 1, "description": "Optional explicit core credential envelope bound to the selected IMAP server." } }, "additionalProperties": false }, "recipients": { "type": "object", "properties": { "from": { "oneOf": [ { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "maxItems": 1, "default": [] }, { "$ref": "#/$defs/recipient" }, { "type": "object", "maxProperties": 0 }, { "type": "null" } ], "description": "Canonical form is an array containing at most one From mailbox. A single recipient object remains accepted for backward compatibility." }, "allow_individual_from": { "type": "boolean", "default": false }, "to": { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "default": [] }, "allow_individual_to": { "type": "boolean", "default": false }, "cc": { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "default": [] }, "allow_individual_cc": { "type": "boolean", "default": false }, "bcc": { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "default": [] }, "allow_individual_bcc": { "type": "boolean", "default": false }, "reply_to": { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "default": [] }, "allow_individual_reply_to": { "type": "boolean", "default": false }, "bounce_to": { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "default": [] }, "allow_individual_bounce_to": { "type": "boolean", "default": false }, "disposition_notification_to": { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "default": [] }, "allow_individual_disposition_notification_to": { "type": "boolean", "default": false } }, "additionalProperties": false, "default": {} }, "template": { "oneOf": [ { "type": "object", "required": [ "subject" ], "properties": { "subject": { "type": "string" }, "text": { "type": [ "string", "null" ] }, "html": { "type": [ "string", "null" ] }, "body_mode": { "type": "string", "enum": [ "text", "html", "both" ], "default": "both", "description": "Which body parts should be generated for campaign messages." } }, "additionalProperties": false }, { "type": "object", "required": [ "source" ], "properties": { "source": { "type": "object", "required": [ "type" ], "properties": { "type": { "type": "string", "const": "files" }, "subject_path": { "type": "string" }, "text_path": { "type": "string" }, "html_path": { "type": "string" }, "encoding": { "type": "string", "default": "utf-8" } }, "additionalProperties": false }, "body_mode": { "type": "string", "enum": [ "text", "html", "both" ], "default": "both", "description": "Which body parts should be generated for campaign messages." } }, "additionalProperties": false } ] }, "attachments": { "type": "object", "properties": { "base_path": { "type": "string", "default": ".", "description": "Legacy single campaign-level attachment base path. Used when attachments.base_paths is absent." }, "base_paths": { "type": "array", "items": { "$ref": "#/$defs/attachment_base_path" }, "default": [], "description": "Named selectable attachment base paths. New WebUI campaigns use these instead of the legacy single base_path." }, "allow_individual": { "type": "boolean", "default": false }, "send_without_attachments": { "type": "boolean", "default": true, "description": "Legacy compatibility flag. Use send_without_attachments_behavior for new campaigns." }, "send_without_attachments_behavior": { "type": "string", "enum": [ "block", "ask", "drop", "continue", "warn" ], "default": "continue", "description": "Campaign-wide behavior when no attachment file is resolved for an active message." }, "zip": { "$ref": "#/$defs/zip_collection_config", "description": "Recipient-level ZIP defaults. Matching attachment rules may inherit, include or exclude themselves." }, "global": { "type": "array", "items": { "$ref": "#/$defs/attachment_config" }, "default": [] }, "missing_behavior": { "type": "string", "enum": [ "block", "ask", "drop", "continue", "warn" ], "default": "ask" }, "ambiguous_behavior": { "type": "string", "enum": [ "block", "ask", "drop", "continue", "warn" ], "default": "ask" } }, "additionalProperties": false, "default": { "base_path": ".", "base_paths": [], "global": [], "zip": { "enabled": false, "archives": [] } } }, "entries": { "oneOf": [ { "type": "object", "required": [ "inline" ], "properties": { "inline": { "type": "array", "items": { "$ref": "#/$defs/entry" } }, "defaults": { "$ref": "#/$defs/entry" }, "imports": { "type": "array", "items": { "$ref": "#/$defs/import_provenance" }, "default": [] } }, "additionalProperties": false }, { "type": "object", "required": [ "source", "mapping" ], "properties": { "source": { "$ref": "#/$defs/source" }, "mapping": { "type": "object", "description": "Internal campaign path -> source column/key. Examples: to.0.email, fields.number, attachments.0.file_filter.", "additionalProperties": { "type": "string" } }, "defaults": { "$ref": "#/$defs/entry" }, "imports": { "type": "array", "items": { "$ref": "#/$defs/import_provenance" }, "default": [] } }, "additionalProperties": false } ] }, "validation_policy": { "type": "object", "properties": { "missing_required_attachment": { "type": "string", "enum": [ "block", "ask", "drop", "continue", "warn" ], "default": "ask" }, "missing_optional_attachment": { "type": "string", "enum": [ "block", "ask", "drop", "continue", "warn" ], "default": "warn" }, "ambiguous_attachment_match": { "type": "string", "enum": [ "block", "ask", "drop", "continue", "warn" ], "default": "ask" }, "ignore_empty_fields": { "type": "boolean", "default": false, "description": "Render unresolved/empty template placeholders as an empty string instead of keeping them and raising a template error." }, "missing_email": { "type": "string", "enum": [ "block", "drop" ], "default": "block" }, "template_error": { "type": "string", "enum": [ "block", "drop" ], "default": "block" }, "inactive_entry": { "type": "string", "enum": [ "drop", "block", "warn" ], "default": "drop" }, "unsent_attachment_files": { "type": "string", "enum": [ "block", "ask", "drop", "continue", "warn" ], "default": "warn", "description": "Behavior when a base path with unsent_warning contains files that are not attached to any message." } }, "additionalProperties": false, "default": { "ignore_empty_fields": false, "unsent_attachment_files": "warn" } }, "delivery": { "type": "object", "properties": { "channel_policy": { "$ref": "#/$defs/delivery_channel_policy" }, "postbox": { "$ref": "#/$defs/postbox_delivery" }, "rate_limit": { "type": "object", "properties": { "messages_per_minute": { "type": "integer", "minimum": 1, "default": 5 }, "concurrency": { "type": "integer", "minimum": 1, "default": 1 } }, "additionalProperties": false }, "imap_append_sent": { "type": "object", "properties": { "enabled": { "type": "boolean", "default": false }, "folder": { "type": "string", "default": "auto" } }, "additionalProperties": false }, "retry": { "type": "object", "properties": { "max_attempts": { "type": "integer", "minimum": 1, "default": 3 }, "backoff_seconds": { "type": "array", "items": { "type": "integer", "minimum": 1 }, "default": [ 60, 300, 900 ] } }, "additionalProperties": false } }, "additionalProperties": false, "default": {} }, "status_tracking": { "type": "object", "properties": { "enabled": { "type": "boolean", "default": true }, "initial_build_status": { "type": "string", "enum": [ "built", "build_failed" ], "default": "built" }, "initial_send_status": { "type": "string", "enum": [ "draft", "queued" ], "default": "draft" } }, "additionalProperties": false, "default": { "enabled": true } } }, "additionalProperties": false, "$defs": { "recipient": { "type": "object", "required": [ "email" ], "properties": { "email": { "type": "string", "format": "email" }, "name": { "type": "string" }, "type": { "type": "string", "enum": [ "to", "cc", "bcc", "reply_to", "bounce_to", "disposition_notification_to" ], "default": "to" } }, "additionalProperties": false }, "delivery_channel_policy": { "type": "string", "enum": [ "mail", "postbox", "mail_and_postbox", "mail_then_postbox", "postbox_then_mail" ], "default": "mail" }, "postbox_target": { "type": "object", "required": [ "id", "mode" ], "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 120 }, "mode": { "type": "string", "enum": [ "direct", "derived" ] }, "label": { "type": [ "string", "null" ], "maxLength": 500 }, "postbox_id": { "type": [ "string", "null" ], "maxLength": 36 }, "address_key": { "type": [ "string", "null" ], "maxLength": 500 }, "template_id": { "type": [ "string", "null" ], "maxLength": 36 }, "organization_unit_id": { "type": [ "string", "null" ], "maxLength": 36 }, "organization_unit_field": { "type": [ "string", "null" ], "maxLength": 255 }, "organization_unit_match": { "type": "string", "enum": [ "id", "slug" ], "default": "id" }, "function_id": { "type": [ "string", "null" ], "maxLength": 36 }, "function_field": { "type": [ "string", "null" ], "maxLength": 255 }, "function_match": { "type": "string", "enum": [ "id", "slug" ], "default": "id" }, "context_key": { "type": [ "string", "null" ], "maxLength": 255 }, "context_field": { "type": [ "string", "null" ], "maxLength": 255 } }, "additionalProperties": false }, "postbox_delivery": { "type": "object", "properties": { "targets": { "type": "array", "items": { "$ref": "#/$defs/postbox_target" }, "maxItems": 50, "default": [] }, "classification": { "type": "string", "minLength": 1, "maxLength": 50, "default": "internal" }, "unresolved_target": { "type": "string", "enum": [ "block", "ask", "drop", "continue", "warn" ], "default": "block" }, "vacant_target": { "type": "string", "enum": [ "block", "ask", "drop", "continue", "warn" ], "default": "warn" }, "duplicate_target": { "type": "string", "enum": [ "block", "ask", "drop", "continue", "warn" ], "default": "warn" } }, "additionalProperties": false, "default": {} }, "attachment_config": { "type": "object", "required": [ "base_dir", "file_filter" ], "properties": { "id": { "type": "string", "description": "Optional stable ID for UI/status references." }, "label": { "type": "string" }, "base_path_id": { "type": [ "string", "null" ], "description": "Stable reference to attachments.base_paths[].id. Preferred over path matching when multiple managed spaces use the same logical path." }, "base_dir": { "type": "string", "description": "Selected attachment base path for current WebUI campaigns, or a directory relative to attachments.base_path for legacy campaigns." }, "file_filter": { "type": "string", "description": "Glob/filter expression, rendered with global/local fields before matching." }, "include_subdirs": { "type": "boolean", "default": false }, "required": { "type": "boolean", "default": true }, "allow_multiple": { "type": "boolean", "default": false, "description": "Legacy compatibility flag. Current UI treats wildcard file_filter patterns as multi-match-capable automatically." }, "missing_behavior": { "type": [ "string", "null" ], "enum": [ "block", "ask", "drop", "continue", "warn", null ], "default": null, "description": "Optional per-rule override. Null or omitted inherits from validation_policy." }, "ambiguous_behavior": { "type": [ "string", "null" ], "enum": [ "block", "ask", "drop", "continue", "warn", null ], "default": null, "description": "Optional per-rule override. Null or omitted inherits from validation_policy." }, "zip": { "$ref": "#/$defs/zip_rule_config" }, "type": { "description": "Legacy UI helper; ignored by backend. Direct files are represented as plain file_filter patterns.", "type": [ "string", "null" ] }, "message_filename_template": { "type": [ "string", "null" ], "default": null, "description": "Optional recipient-rendered filename template used when this rule sends files directly as message attachments. If omitted, the source filename is used." }, "zip_entry_name_template": { "type": [ "string", "null" ], "default": null, "description": "Optional recipient-rendered filename template used for this rule's files inside recipient ZIP archives. If omitted, the source filename is used." } }, "additionalProperties": false }, "entry": { "type": "object", "properties": { "id": { "type": "string" }, "active": { "type": "boolean", "default": true }, "from": { "oneOf": [ { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "maxItems": 1, "default": [] }, { "$ref": "#/$defs/recipient" }, { "type": "object", "maxProperties": 0 }, { "type": "null" } ], "description": "Canonical form is an array containing at most one From mailbox. A single recipient object remains accepted for backward compatibility." }, "merge_from": { "type": "boolean", "default": false, "description": "Deprecated compatibility field. Recipient-specific From always overrides the campaign From value and never merges." }, "to": { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "default": [] }, "merge_to": { "type": "boolean", "default": true, "description": "When individual to values are allowed, merge global and recipient values instead of overriding the global values." }, "combine_to": { "type": "boolean", "default": true, "description": "Deprecated compatibility alias for merge_to. New campaign JSON should use merge_*." }, "cc": { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "default": [] }, "merge_cc": { "type": "boolean", "default": true, "description": "When individual cc values are allowed, merge global and recipient values instead of overriding the global values." }, "combine_cc": { "type": "boolean", "default": true, "description": "Deprecated compatibility alias for merge_cc. New campaign JSON should use merge_*." }, "bcc": { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "default": [] }, "merge_bcc": { "type": "boolean", "default": true, "description": "When individual bcc values are allowed, merge global and recipient values instead of overriding the global values." }, "combine_bcc": { "type": "boolean", "default": true, "description": "Deprecated compatibility alias for merge_bcc. New campaign JSON should use merge_*." }, "reply_to": { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "default": [] }, "merge_reply_to": { "type": "boolean", "default": true, "description": "When individual reply-to values are allowed, merge global and recipient values instead of overriding the global values." }, "combine_reply_to": { "type": "boolean", "default": true, "description": "Deprecated compatibility alias for merge_reply_to. New campaign JSON should use merge_*." }, "bounce_to": { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "default": [] }, "merge_bounce_to": { "type": "boolean", "default": true, "description": "When individual bounce-to values are allowed, merge global and recipient values instead of overriding the global values." }, "combine_bounce_to": { "type": "boolean", "default": true, "description": "Deprecated compatibility alias for merge_bounce_to. New campaign JSON should use merge_*." }, "disposition_notification_to": { "type": "array", "items": { "$ref": "#/$defs/recipient" }, "default": [] }, "merge_disposition_notification_to": { "type": "boolean", "default": true, "description": "When individual disposition-notification-to values are allowed, merge global and recipient values instead of overriding the global values." }, "combine_disposition_notification_to": { "type": "boolean", "default": true, "description": "Deprecated compatibility alias for merge_disposition_notification_to. New campaign JSON should use merge_*." }, "channel_policy": { "oneOf": [ { "$ref": "#/$defs/delivery_channel_policy" }, { "type": "null" } ], "default": null }, "postbox_targets": { "type": "array", "items": { "$ref": "#/$defs/postbox_target" }, "maxItems": 50, "default": [] }, "merge_postbox_targets": { "type": "boolean", "default": true }, "attachments": { "type": "array", "items": { "$ref": "#/$defs/attachment_config" }, "default": [] }, "combine_attachments": { "type": "boolean", "default": true }, "fields": { "type": "object", "additionalProperties": true, "default": {} }, "last_sent": { "type": "string", "format": "date-time" }, "name": { "type": [ "string", "null" ] }, "email": { "type": [ "string", "null" ], "format": "email" } }, "additionalProperties": false }, "source": { "type": "object", "required": [ "type", "path" ], "properties": { "type": { "type": "string", "enum": [ "csv", "json" ] }, "path": { "type": "string" }, "delimiter": { "type": "string", "default": ";" }, "encoding": { "type": "string", "default": "utf-8" }, "has_header": { "type": "boolean", "default": true } }, "additionalProperties": false }, "import_provenance": { "type": "object", "required": [ "id", "imported_at", "mode", "source_type", "header_rows", "rows_total", "valid_rows", "invalid_rows", "imported_rows" ], "properties": { "id": { "type": "string" }, "imported_at": { "type": "string", "format": "date-time" }, "mode": { "type": "string", "enum": [ "append", "replace" ] }, "source_type": { "type": "string", "enum": [ "csv", "xlsx", "text", "addresses" ] }, "source_id": { "type": [ "string", "null" ] }, "source_label": { "type": [ "string", "null" ] }, "source_revision": { "type": [ "string", "null" ] }, "source_provenance": { "type": "object", "default": {} }, "filename": { "type": [ "string", "null" ] }, "sheet_name": { "type": [ "string", "null" ] }, "encoding": { "type": [ "string", "null" ] }, "delimiter": { "type": [ "string", "null" ] }, "header_rows": { "type": "integer", "minimum": 0, "default": 0 }, "quoted": { "type": [ "boolean", "null" ] }, "value_separators": { "type": [ "string", "null" ] }, "rows_total": { "type": "integer", "minimum": 0 }, "valid_rows": { "type": "integer", "minimum": 0 }, "invalid_rows": { "type": "integer", "minimum": 0 }, "imported_rows": { "type": "integer", "minimum": 0 }, "field_names_created": { "type": "array", "items": { "type": "string" }, "default": [] }, "attachment_patterns": { "type": "integer", "minimum": 0, "default": 0 }, "mapping": { "type": "array", "items": { "type": "object", "required": [ "column_index", "header", "kind" ], "properties": { "column_index": { "type": "integer", "minimum": 0 }, "header": { "type": "string" }, "kind": { "type": "string" }, "field_name": { "type": [ "string", "null" ] }, "new_field_name": { "type": [ "string", "null" ] } }, "additionalProperties": false }, "default": [] } }, "additionalProperties": false }, "attachment_base_path": { "type": "object", "required": [ "name", "path" ], "properties": { "id": { "type": "string", "description": "Optional stable ID for UI/status references." }, "name": { "type": "string", "description": "Display name for this selectable attachment base path." }, "path": { "type": "string", "default": ".", "description": "Base path relative to the campaign file unless absolute." }, "allow_individual": { "type": "boolean", "default": false, "description": "Whether recipient-level attachments may use this base path." }, "source": { "type": [ "string", "null" ], "description": "Legacy UI compatibility value. Ignored by the backend." }, "unsent_warning": { "type": "boolean", "default": false, "description": "Warn according to validation_policy.unsent_attachment_files if files in this source are not attached to any built message." } }, "additionalProperties": false }, "zip_config": { "type": "object", "properties": { "enabled": { "type": "boolean", "default": false }, "mode": { "type": "string", "enum": [ "inherit", "include", "exclude" ], "default": "inherit", "description": "Per-attachment archive behavior. Campaign-level mode is accepted for schema compatibility but ignored." }, "filename_template": { "type": [ "string", "null" ], "description": "Recipient-level ZIP filename. Campaign/global/local placeholders are supported." }, "password_mode": { "type": "string", "enum": [ "none", "direct", "field", "template" ], "default": "none" }, "password": { "type": [ "string", "null" ], "description": "Fixed ZIP password when password_mode is direct." }, "password_field": { "type": [ "string", "null" ], "description": "Campaign field containing the recipient-specific password when password_mode is field." }, "password_template": { "type": [ "string", "null" ], "description": "Legacy/advanced password template when password_mode is template." }, "method": { "type": "string", "enum": [ "zip_standard", "aes" ], "default": "aes" } }, "additionalProperties": false, "default": { "enabled": false, "mode": "inherit", "password_mode": "none", "method": "aes" } }, "zip_archive_config": { "type": "object", "required": [ "id", "name" ], "properties": { "id": { "type": "string" }, "name": { "type": "string", "default": "attachments.zip" }, "standard": { "type": "boolean", "default": false }, "password_enabled": { "type": "boolean", "default": false }, "password_field": { "type": [ "string", "null" ] }, "password_scope": { "type": "string", "enum": [ "local", "global" ], "default": "local" }, "method": { "type": "string", "enum": [ "zip_standard", "aes" ], "default": "aes" }, "password_mode": { "type": [ "string", "null" ], "enum": [ "none", "direct", "field", "template", null ] }, "password": { "type": [ "string", "null" ] }, "password_template": { "type": [ "string", "null" ] } }, "additionalProperties": false }, "zip_collection_config": { "type": "object", "properties": { "enabled": { "type": "boolean", "default": false }, "archives": { "type": "array", "items": { "$ref": "#/$defs/zip_archive_config" }, "default": [] }, "filename_template": { "type": [ "string", "null" ] }, "password_mode": { "type": "string", "enum": [ "none", "direct", "field", "template" ], "default": "none" }, "password": { "type": [ "string", "null" ] }, "password_field": { "type": [ "string", "null" ] }, "password_template": { "type": [ "string", "null" ] }, "method": { "type": "string", "enum": [ "zip_standard", "aes" ], "default": "aes" } }, "additionalProperties": false, "default": { "enabled": false, "archives": [] } }, "zip_rule_config": { "type": "object", "properties": { "archive_id": { "type": "string", "default": "inherit", "description": "inherit, exclude, or a configured archive id" }, "enabled": { "type": "boolean", "default": false }, "mode": { "type": "string", "enum": [ "inherit", "include", "exclude" ], "default": "inherit" }, "filename_template": { "type": [ "string", "null" ] }, "password_mode": { "type": "string", "enum": [ "none", "direct", "field", "template" ], "default": "none" }, "password": { "type": [ "string", "null" ] }, "password_field": { "type": [ "string", "null" ] }, "password_template": { "type": [ "string", "null" ] }, "method": { "type": "string", "enum": [ "zip_standard", "aes" ], "default": "aes" } }, "additionalProperties": false, "default": { "archive_id": "inherit" } } } }