openapi: 3.0.0
info:
    version: '0.0'
    title: 'Qomon Emailer'
    description: "This is Qomon's Emailer API definition.\n\nTo get an authorization go to [Qomon's setting page](https://qomon.app/settings/extensions/connect) and create an API key."
servers:
    -
        description: 'Qomon API'
        url: 'https://incoming.qomon.app'
tags:
    -
        name: Account
        description: "An account is uniquely identified within a group.\nIt is a composition of Provider, Senders, Domains and Templates linked to it."
    -
        name: Campaign
        description: "A campaign is bulk emails for marketing purpose.\nIt is a different process to call on providers and improves deliverability."
    -
        name: Email
        description: 'Manage emails and informations about it.'
    -
        name: Template
        description: 'Manage templates and view.'
    -
        name: Senders
        description: 'Manage email senders, including listing, adding, validating, and removing senders for an account.'
    -
        name: Domains
        description: 'Manage email domains, including adding, verifying DNS records, and configuring domain settings for email delivery.'
    -
        name: Autosend
        description: 'Manage the automatic ("smart") email settings of a group: subject, header, footer, gender labels and sender used by emails triggered by survey answers.'
paths:
    /emailer/account:
        get:
            tags: [Account]
            summary: 'Retrieve all informations of an account at once'
            responses: {'200': {description: 'Account information retrieved.', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, properties: {CreatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, UpdatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, DeletedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z', nullable: true}, id: {type: integer}, group_id: {type: integer}, credits: {type: integer}, provider_id: {type: integer}, provider: {$ref: '#/components/schemas/EmailerProvider'}, senders: {$ref: '#/components/schemas/EmailerSenders'}, domains: {type: array, items: {$ref: '#/components/schemas/EmailerDomain'}}, credits_refill: {$ref: '#/components/schemas/EmailerCreditRefill'}, subscription: {$ref: '#/components/schemas/EmailerSubscription'}, default_flag: {type: boolean}, sub_account_id: {type: integer, description: 'ID of the sub-account, can be used to know if account is legacy', nullable: true}}}}}}}}, '400': {description: 'Bad request - invalid parameters', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {missingGroupData: {summary: 'Missing group data', value: {status: error, message: 'mailjetapipublickey not found in group data'}}}}}}, '404': {description: 'Account not found', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {accountNotFound: {summary: 'Account not found for the group', value: {status: error, message: emailer_account_not_found}}}}}}, '500': {description: 'Internal server error', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {internalError: {summary: 'Internal server error', value: {status: error, message: failed_to_retrieve_account}}}}}}, '502': {description: 'Bad gateway - provider API error', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {providerGetCreditsFailed: {summary: 'Failed to retrieve credits from provider', value: {status: error, message: provider_get_credits_failed}}, providerSyncDomainsFailed: {summary: 'Failed to sync domains from provider', value: {status: error, message: provider_sync_domains_failed}}, providerSyncSendersFailed: {summary: 'Failed to sync senders from provider', value: {status: error, message: provider_sync_senders_failed}}}}}}}
        patch:
            tags: [Account]
            summary: 'Update emailer account settings'
            description: "Update various settings for an emailer account. This endpoint allows you to modify account configuration\nsuch as unsubscribe URL, CC removal settings, deduplication settings, and credit refill configuration.\n\n**Note**: The ProviderID is automatically set to 1 (ElasticEmail) for all updates.\n"
            requestBody: {required: true, content: {application/json: {schema: {type: object, properties: {emailer_account: {type: object, properties: {id: {type: integer, description: 'Account ID (optional, will be inferred from group context)', example: 123}, group_id: {type: integer, description: 'Group ID (optional, will be inferred from authentication context)', example: 456}, provider_id: {type: integer, description: 'Provider ID (automatically set to 1 for ElasticEmail)', example: 1}, unsubscribe_url: {type: string, description: 'Custom unsubscribe URL for emails', example: 'https://example.com/unsubscribe'}, remove_cc: {type: boolean, description: 'Whether to remove CC recipients from emails', example: true}, dedup_while_sending: {type: boolean, description: 'Whether to deduplicate contacts while sending', example: false}, credits_refill: {type: object, properties: {CreatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, DeletedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z', nullable: true}, UpdatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, account_id: {type: integer}, amount: {type: integer}, anniversary: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, id: {type: integer}, refill_day: {type: integer}, refill_next: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, refill_until: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}}}, subscription: {type: object, properties: {CreatedAt: {type: string, format: date-time, description: 'The timestamp when the subscription was created'}, UpdatedAt: {type: string, format: date-time, description: 'The timestamp when the subscription was last updated'}, DeletedAt: {type: string, format: date-time, description: 'The timestamp when the subscription was deleted (if soft-deleted)', nullable: true}, id: {type: integer, description: 'Unique identifier for the subscription'}, account_id: {type: integer, description: 'Identifier for the associated account'}, automatic_upsell: {type: boolean, description: 'Indicates whether automatic upsell is enabled'}, no_stripe: {type: boolean, description: 'Indicates whether the subscription uses Stripe for payment'}, pack_count: {type: integer, description: 'Number of credit packs in the subscription', example: 2}, free_credit_count: {type: integer, description: 'Number of free credits included in the subscription', example: 1000}}}}}}}, examples: {updateUnsubscribeUrl: {summary: 'Update unsubscribe URL', value: {emailer_account: {unsubscribe_url: 'https://mycompany.com/unsubscribe'}}}, updateEmailSettings: {summary: 'Update email sending settings', value: {emailer_account: {remove_cc: true, dedup_while_sending: false}}}, updateCreditsRefill: {summary: 'Update credit refill settings', value: {emailer_account: {credits_refill: {amount: 5000, refill_next: '2024-12-01T00:00:00Z', refill_until: '2025-12-01T00:00:00Z'}}}}}}}}
            responses: {'200': {description: 'Account successfully updated', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, properties: {CreatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, UpdatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, DeletedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z', nullable: true}, id: {type: integer}, group_id: {type: integer}, credits: {type: integer}, provider_id: {type: integer}, provider: {$ref: '#/components/schemas/EmailerProvider'}, senders: {$ref: '#/components/schemas/EmailerSenders'}, domains: {type: array, items: {$ref: '#/components/schemas/EmailerDomain'}}, credits_refill: {$ref: '#/components/schemas/EmailerCreditRefill'}, subscription: {$ref: '#/components/schemas/EmailerSubscription'}, default_flag: {type: boolean}, sub_account_id: {type: integer, description: 'ID of the sub-account, can be used to know if account is legacy', nullable: true}}}}}, examples: {updateSuccess: {summary: 'Successfully updated account', value: {status: success, data: {id: 123, group_id: 456, provider_id: 1, unsubscribe_url: 'https://mycompany.com/unsubscribe', remove_cc: true, dedup_while_sending: false, credits_refill: {amount: 5000, refill_next: '2024-12-01T00:00:00Z', refill_until: '2025-12-01T00:00:00Z'}, subscription: {automatic_upsell: false, no_stripe: false, pack_count: 2, free_credit_count: 1000}}}}}}}}, '400': {description: 'Bad request - invalid parameters', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {invalidRequest: {summary: 'Invalid request format', value: {status: error, message: 'Invalid request format'}}}}}}, '401': {description: 'Unauthorized - authentication required', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {unauthorized: {summary: 'User not authenticated', value: {status: error, message: 'User must authenticate before using this api'}}}}}}, '404': {description: 'Account not found', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {accountNotFound: {summary: 'Account not found for the group', value: {status: error, message: 'Account not found'}}}}}}, '500': {description: 'Internal server error', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {internalError: {summary: 'Internal server error', value: {status: error, message: 'An internal error occurred'}}}}}}}
    /emailer/account/import:
        post:
            tags: [Account]
            summary: 'Import an existing Brevo API key'
            description: "Import an existing standalone Brevo account by providing its API key. This endpoint allows administrators\nto connect their own Brevo account to Qomon instead of using a Qomon-managed sub-account.\n\nThe API key must:\n- Start with 'xkeysib-'\n- Be valid and accessible (not IP-restricted)\n\nIf a Brevo account already exists for this group, it will be replaced with the new standalone account.\nThis operation will:\n- Update the API key\n- Set the account as standalone (no_stripe = true)\n- Disable automatic billing/refill\n- Clear sub-account metadata\n"
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [api_key], properties: {api_key: {type: string, description: 'The Brevo API key to import (must start with ''xkeysib-'')', example: xkeysib-1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef}}}, examples: {importApiKey: {summary: 'Import a Brevo API key', value: {api_key: xkeysib-1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef}}}}}}
            responses: {'200': {description: 'Brevo API key successfully imported and configured', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, properties: {message: {type: string, example: 'Brevo API key successfully imported and configured'}}}}}, examples: {importSuccess: {summary: 'Successfully imported API key', value: {status: success, data: {message: 'Brevo API key successfully imported and configured'}}}}}}}, '401': {description: 'Unauthorized - authentication required', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {unauthorized: {summary: 'User not authenticated', value: {status: error, message: 'User must authenticate before using this api'}}}}}}, '406': {description: 'Not acceptable - invalid or missing API key', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {missingApiKey: {summary: 'API key is required', value: {status: error, message: 'api_key is required'}}, invalidFormat: {summary: 'Invalid API key format', value: {status: error, message: 'invalid Brevo API key format (must start with ''xkeysib-'')'}}, validationFailed: {summary: 'API key validation failed', value: {status: error, message: 'failed to validate Brevo API key: unauthorized: invalid API key or IP restriction'}}}}}}, '500': {description: 'Internal server error', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {groupRetrievalFailed: {summary: 'Failed to retrieve group information', value: {status: error, message: 'failed to retrieve group information'}}, apiKeyValidationError: {summary: 'Error during API key validation', value: {status: error, message: 'failed to validate Brevo API key: internal error'}}, accountReplaceFailed: {summary: 'Failed to replace existing account', value: {status: error, message: 'failed to replace emailer account: database error'}}, accountCreateFailed: {summary: 'Failed to create new account', value: {status: error, message: 'failed to create emailer account: database error'}}, groupUpdateFailed: {summary: 'Failed to update group settings', value: {status: error, message: 'failed to update group: database error'}}}}}}}
    /emailer/account/credits:
        post:
            tags: [Account]
            summary: 'Set or add credits to an emailer account'
            description: "Manage credits for an emailer account. This endpoint allows you to either set the total credit count \nor add additional credits to the current account.\n\n- **set**: Sets the total credit count to the specified amount\n- **add**: Adds the specified amount to the current credit count\n\nThe operation will sync with the billing service and update the account accordingly.\n"
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [method, amount], properties: {method: {type: string, enum: [set, add], description: "The operation to perform on the account credits.\n- `set` - Set the total credit count to the specified amount\n- `add` - Add the specified amount to the current credit count\n", example: set}, amount: {type: integer, minimum: 1, description: 'The amount of credits to set or add', example: 10000}}}, examples: {setCredits: {summary: 'Set total credits to 10,000', value: {method: set, amount: 10000}}, addCredits: {summary: 'Add 5,000 credits to current balance', value: {method: add, amount: 5000}}}}}}
            responses: {'200': {description: 'Credits successfully updated', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, properties: {success: {type: boolean, example: true}, message: {type: string, example: 'Credits set successfully'}, method: {type: string, enum: [set, add], example: set}, amount: {type: integer, example: 10000}}}}}, examples: {setSuccess: {summary: 'Successfully set credits', value: {status: success, data: {success: true, message: 'Credits set successfully', method: set, amount: 10000}}}, addSuccess: {summary: 'Successfully added credits', value: {status: success, data: {success: true, message: 'Credits added successfully', method: add, amount: 5000}}}}}}}, '400': {description: 'Bad request - invalid parameters', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {invalidMethod: {summary: 'Invalid method parameter', value: {status: error, message: 'method must be either ''set'' or ''add'''}}, invalidAmount: {summary: 'Invalid amount parameter', value: {status: error, message: 'amount must be greater than 0'}}, invalidCreditAmount: {summary: 'Invalid credit amount from billing service', value: {status: error, message: invalid_credit_amount}}, targetCreditsLessThanFree: {summary: 'Target credits less than free credits', value: {status: error, message: target_credits_less_than_free}}, invalidGroupId: {summary: 'Invalid group ID', value: {status: error, message: invalid_group_id}}}}}}, '401': {description: 'Unauthorized - authentication required', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {unauthorized: {summary: 'User not authenticated', value: {status: error, message: 'User must authenticate before using this api'}}}}}}, '404': {description: 'Account not found', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {accountNotFound: {summary: 'Account not found for the group', value: {status: error, message: account_not_found}}, subscriptionNotFound: {summary: 'Subscription not found for the group', value: {status: error, message: subscription_not_found}}}}}}, '412': {description: 'Precondition failed', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {brevoStandaloneAccount: {summary: 'Brevo standalone account', value: {status: error, message: brevo_standalone_account}}}}}}, '500': {description: 'Internal server error', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {billingServiceError: {summary: 'Billing service error', value: {status: error, message: billing_service_error}}, failedToRetrieveSubscription: {summary: 'Failed to retrieve subscription', value: {status: error, message: failed_to_retrieve_subscription}}, failedToUpdateSubscription: {summary: 'Failed to update subscription', value: {status: error, message: failed_to_update_subscription}}, failedToGetTaxRate: {summary: 'Failed to get tax rate', value: {status: error, message: failed_to_get_tax_rate}}, failedToSaveAccount: {summary: 'Failed to save account', value: {status: error, message: failed_to_save_account}}}}}}}
    /emailer/account/login:
        get:
            tags: [Email]
            summary: 'Retrieve provider login URL for the account'
            description: 'Get a login URL for the email provider associated with the account (for OAuth or similar flows).'
            responses: {'200': {description: 'Successfully retrieved login URL.', content: {application/json: {schema: {type: object, properties: {login_url: {type: string, description: 'Login URL for the provider'}}}, examples: {loginUrlExample: {summary: 'Example login URL response', value: {login_url: 'https://provider.com/oauth/authorize?client_id=abc123'}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/account/status:
        get:
            tags: [Account]
            summary: 'Get emailer account status'
            description: "Retrieve the current status of the emailer account, including campaign and SMTP status.\nThis endpoint checks for any rejected campaigns and returns appropriate status indicators.\n"
            responses: {'200': {description: 'Successfully retrieved account status', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, properties: {campaign_status: {type: string, enum: [ok, not_ok], description: "Campaign status indicator:\n- `ok` - No issues with campaigns\n- `not_ok` - One or more campaigns have been rejected\n", example: ok}, smtp_status: {type: string, enum: [ok, not_ok], description: "SMTP status indicator:\n- `ok` - SMTP service is functioning normally\n- `not_ok` - There are issues with SMTP service\n", example: ok}}}}}, examples: {statusOk: {summary: 'Account status is healthy', value: {status: success, data: {campaign_status: ok, smtp_status: ok}}}, statusWithRejectedCampaigns: {summary: 'Account has rejected campaigns', value: {status: success, data: {campaign_status: not_ok, smtp_status: ok}}}}}}}, '400': {description: 'Bad request - invalid parameters', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {missingGroupId: {summary: 'Missing group ID', value: {status: error, message: group_id_missing}}}}}}, '401': {description: 'Unauthorized - authentication required', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {unauthorized: {summary: 'User not authenticated', value: {status: error, message: 'User must authenticate before using this api'}}}}}}, '500': {description: 'Internal server error', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {internalError: {summary: 'Failed to check account status', value: {status: error, message: failed_to_check_rejected_campaigns}}}}}}}
    '/emailer/subscription/refresh/{groupId}':
        get:
            tags: [Account]
            summary: 'Refresh subscription and resync provider credits'
            description: "Force-refresh the subscription from the billing service and adjust provider credits to match the billed total minus already used credits.\nRequires ultradmin privileges.\n"
            x-permissions: [ultradmin]
            parameters: [{in: path, name: groupId, required: true, schema: {type: integer}, description: 'Group ID'}]
            responses: {'200': {description: 'Subscription refreshed and credits synchronized', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, properties: {success: {type: boolean, example: true}}}}}, examples: {refreshSuccess: {summary: 'Successfully refreshed subscription', value: {status: success, data: {success: true}}}}}}}, '401': {description: 'Unauthorized - authentication required', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {unauthorized: {summary: 'User not authenticated', value: {status: error, message: 'User must authenticate before using this api'}}}}}}, '403': {description: 'Forbidden - ultradmin required', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {forbidden: {summary: 'User lacks ultradmin privileges', value: {status: error, message: forbidden}}}}}}, '404': {description: 'Account not found', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {accountNotFound: {summary: 'Account not found for the group', value: {status: error, message: account_not_found}}}}}}, '500': {description: 'Internal server error', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {billingServiceError: {summary: 'Billing service error', value: {status: error, message: billing_service_error}}, failedToRetrieveSubscription: {summary: 'Failed to retrieve subscription', value: {status: error, message: failed_to_retrieve_subscription}}, failedToSaveAccount: {summary: 'Failed to save account', value: {status: error, message: failed_to_save_account}}}}}}}
    '/emailer/campaign/{campaignId}':
        parameters:
            - {name: campaignId, description: 'The unique identifier of the campaign in Qomon', in: path, required: true, example: 198, schema: {type: integer}}
        get:
            description: "Retrieve details about a campaign.\nThe response in the process of being created."
            tags: [Campaign]
            responses: {'200': {description: 'Campaign corresponding to the provided id', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {allOf: [{$ref: '#/components/schemas/EmailerCampaignView'}, {type: object, properties: {links: {type: array, items: {$ref: '#/components/schemas/CampaignStepLink'}}}}]}}}]}, examples: {getCampaignById: {summary: 'Example response for a single campaign', value: {status: success, data: {id: 198, goal: announcement, campaign_name: 'Product Launch Campaign', sender: {id: 123, email: sender@qomon.com, name: 'Marketing Team', validated: true}, template: {id: 456, name: 'Product Launch Template', content: '<!DOCTYPE html><html><body><h1>New Product Launch</h1></body></html>'}, user_id: 42, group_id: 1, status: started, contacts_selected: 5000, created_at: '2024-01-15T10:00:00Z', started_at: '2024-01-15T14:00:00Z', performance_rating: 0.85, is_successful: true}}}, getCampaignByIdArchived: {summary: 'Example response for a single campaign (archived)', value: {status: success, data: {id: 198, goal: announcement, campaign_name: 'Product Launch Campaign', sender: {id: 123, email: sender@qomon.com, name: 'Marketing Team', validated: true}, template: {id: 456, name: 'Product Launch Template', content: '<!DOCTYPE html><html><body><h1>New Product Launch</h1></body></html>'}, user_id: 42, group_id: 1, status: archived, contacts_selected: 5000, created_at: '2024-01-15T10:00:00Z', started_at: '2024-01-15T14:00:00Z', performance_rating: 0.85, is_successful: true, delivered_count: 1000, open_count: 500, unique_click_count: 100, hard_bounce_count: 10, unsubscribe_count: 10, spam_complaints_count: 10}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
        delete:
            tags: [Campaign]
            summary: 'Delete or archive a campaign'
            description: "Deletes or archives a campaign based on its current status. The behavior depends on the campaign's current status:\n\n**Deletion Rules:**\n- **Draft, Failed, Cancelled, Rejected**: Campaign is permanently deleted (soft delete)\n- **Started**: Campaign is archived (status changed to `archived`)\n- **Scheduled, Running, Pending**: Returns error (cannot be deleted)\n\n**Status-Specific Behavior:**\n- **Deletable Statuses**: `draft`, `failed`, `cancelled`, `rejected` → Campaign is removed from the system\n- **Archivable Statuses**: `started` → Campaign status is changed to `archived` (preserved for historical data)\n- **Protected Statuses**: `scheduled`, `running`, `pending` → Operation not allowed\n\n**Use Cases:**\n- Clean up draft campaigns that are no longer needed\n- Remove failed campaigns that cannot be recovered\n- Archive completed campaigns to preserve historical data\n- Remove cancelled or rejected campaigns that are no longer relevant"
            responses: {'200': {description: 'Campaign successfully deleted or archived', content: {application/json: {schema: {type: object, required: [status], properties: {status: {type: string, enum: [success]}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '406': {description: 'Campaign cannot be deleted in current status', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [error, fail]}}}, {type: object, properties: {error: {type: string, example: campaign_cannot_be_deleted_in_current_status}}}]}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/campaign/status:
        patch:
            tags: [Campaign]
            summary: 'Update campaign status'
            description: "Updates the status of an existing campaign. Currently supports cancelling scheduled or running campaigns.\n\n**Supported Status Updates:**\n- `cancelled` - Cancels a scheduled or running campaign\n\n**Status Validation:**\n- Only campaigns with status `scheduled` or `running` can be cancelled\n- The system will update both the provider (Brevo/Sendinblue) and local database\n- Invalid status transitions will return an error\n\n**Provider Integration:**\n- Automatically calls the communication service to update the campaign status in the email provider\n- Ensures consistency between Qomon and the email provider"
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [status], properties: {id: {type: integer, description: 'ID of the campaign', example: 198}, status: {type: string, enum: [cancelled], description: 'New status for the campaign', example: cancelled}}}, examples: {cancelCampaign: {summary: 'Cancel a running campaign', value: {id: 198, status: cancelled}}}}}}
            responses: {'200': {description: 'Campaign status updated successfully', content: {application/json: {schema: {type: object, required: [status], properties: {status: {type: string, enum: [success]}}}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '406': {description: 'Campaign cannot be updated in current status', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [error, fail]}}}, {type: object, properties: {error: {type: string, example: campaign_cannot_be_cancelled_in_current_status}}}]}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    '/v2/kpi/emailer/campaigns/{id}':
        get:
            tags: [Campaign]
            summary: 'Get detailed campaign KPI statistics'
            description: 'Retrieves comprehensive performance metrics and statistics for a campaign including delivery rates, open rates, click rates, and more.'
            parameters: [{name: id, in: path, description: 'ID of the campaign to get KPI statistics for', required: true, schema: {type: integer, example: 198}}]
            responses: {'200': {description: 'Campaign KPI statistics retrieved successfully', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, properties: {total_count: {type: integer, description: 'Total number of campaign events', example: 1250}, sent_count: {type: integer, description: 'Number of emails sent', example: 1200}, delivered_count: {type: integer, description: 'Number of emails delivered', example: 1180}, delivered_rate: {type: number, format: float, description: 'Delivery rate as percentage (0-100)', example: 98.3}, open_count: {type: integer, description: 'Number of unique emails opened', example: 305}, open_rate: {type: number, format: float, description: 'Open rate as percentage (0-100)', example: 25.4}, total_open_count: {type: integer, description: 'Total number of opens, including repeat opens by the same recipient', example: 412}, click_count: {type: integer, description: 'Number of unique clicks', example: 38}, click_rate: {type: number, format: float, description: 'Click rate as percentage (0-100)', example: 3.2}, total_click_count: {type: integer, description: 'Total number of clicks, including repeat clicks by the same recipient', example: 45}, performance_rating: {type: number, format: float, description: 'Performance rating as percentage (0-100)', example: 85.2}, unsubscribe_count: {type: integer, description: 'Number of unsubscribes', example: 10}, unsubscribe_rate: {type: number, format: float, description: 'Unsubscribe rate as percentage (0-100)', example: 0.8}, bounce_count: {type: integer, description: 'Number of bounces (hard and soft)', example: 20}, bounce_rate: {type: number, format: float, description: 'Bounce rate as percentage (0-100)', example: 1.7}, hard_bounce_count: {type: integer, description: 'Number of hard (permanent) bounces', example: 14}, hard_bounce_rate: {type: number, format: float, description: 'Hard bounce rate as percentage (0-100)', example: 1.2}, soft_bounce_count: {type: integer, description: 'Number of soft (temporary) bounces', example: 6}, soft_bounce_rate: {type: number, format: float, description: 'Soft bounce rate as percentage (0-100)', example: 0.5}, abuse_count: {type: integer, description: 'Number of abuse reports', example: 2}, abuse_rate: {type: number, format: float, description: 'Abuse rate as percentage (0-100)', example: 0.2}}}}}]}, examples: {campaignKpiStats: {summary: 'Example response for campaign KPI statistics', value: {status: success, data: {total_count: 1250, sent_count: 1200, delivered_count: 1180, delivered_rate: 98.3, open_count: 305, open_rate: 25.4, total_open_count: 412, click_count: 38, click_rate: 3.2, total_click_count: 45, performance_rating: 85.2, unsubscribe_count: 10, unsubscribe_rate: 0.8, bounce_count: 20, bounce_rate: 1.7, hard_bounce_count: 14, hard_bounce_rate: 1.2, soft_bounce_count: 6, soft_bounce_rate: 0.5, abuse_count: 2, abuse_rate: 0.2, overall_delivered_diff: 10, overall_open_diff: 10, overall_click_diff: 10.2, overall_unsubscribe_diff: -10, overall_bounce_diff: 10, overall_abuse_diff: -10}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /v2/kpi/emailer/campaigns/aggregate:
        get:
            tags: [Campaign]
            summary: 'Get aggregate campaign KPI statistics with advanced filtering'
            description: "Retrieves aggregate performance metrics and statistics for campaigns with flexible filtering options.\nCompares current 30 days performance against previous 3 months average.\n\n**Filter Types:**\n- **Exact Match**: goal, status, is_successful\n- **Text Search**: campaign_name (case-insensitive partial match)\n- **Date Ranges**: started_at, created_at (supports from/to)\n- **Number Ranges**: contacts_selected, performance_rating (supports min/max)\n- **List Filters**: sender_ids, user_ids, saved_filter_ids, template_ids (comma-separated)"
            parameters: [{name: goal, in: query, description: 'Campaign goal to filter by', required: false, schema: {type: string, example: announcement}}, {name: status, in: query, description: 'Campaign status to filter by', required: false, schema: {type: string, enum: [draft, pending, running, rejected, scheduled, cancelled, started, archived, failed], example: started}}, {name: is_successful, in: query, description: 'Filter by campaign success status', required: false, schema: {type: boolean, example: true}}, {name: campaign_name, in: query, description: 'Campaign name to search for (case-insensitive partial match)', required: false, schema: {type: string, example: 'Black Friday'}}, {name: started_at_from, in: query, description: 'Filter campaigns started from this date (inclusive)', required: false, schema: {type: string, format: date, example: '2024-01-01'}}, {name: started_at_to, in: query, description: 'Filter campaigns started until this date (inclusive)', required: false, schema: {type: string, format: date, example: '2024-12-31'}}, {name: created_at_from, in: query, description: 'Filter campaigns created from this date (inclusive)', required: false, schema: {type: string, format: date, example: '2024-01-01'}}, {name: created_at_to, in: query, description: 'Filter campaigns created until this date (inclusive)', required: false, schema: {type: string, format: date, example: '2024-12-31'}}, {name: contacts_selected_min, in: query, description: 'Minimum number of contacts selected', required: false, schema: {type: integer, minimum: 0, example: 100}}, {name: contacts_selected_max, in: query, description: 'Maximum number of contacts selected', required: false, schema: {type: integer, minimum: 1, example: 10000}}, {name: performance_rating_min, in: query, description: 'Minimum performance rating (0.0-1.0)', required: false, schema: {type: number, format: float, minimum: 0, maximum: 1, example: 0.7}}, {name: performance_rating_max, in: query, description: 'Maximum performance rating (0.0-1.0)', required: false, schema: {type: number, format: float, minimum: 0, maximum: 1, example: 1}}, {name: sender_ids, in: query, description: 'Comma-separated list of sender IDs to filter by', required: false, schema: {type: string, example: '123,456,789'}}, {name: user_ids, in: query, description: 'Comma-separated list of user IDs to filter by', required: false, schema: {type: string, example: '100,200,300'}}, {name: saved_filter_ids, in: query, description: 'Comma-separated list of saved filter IDs to filter by', required: false, schema: {type: string, example: '50,60,70'}}, {name: template_ids, in: query, description: 'Comma-separated list of template IDs to filter by', required: false, schema: {type: string, example: '10,20,30'}}]
            responses: {'200': {description: 'Aggregate campaign KPI statistics retrieved successfully', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, properties: {group_id: {type: integer, description: 'ID of the group', example: 1}, filters: {$ref: '#/components/schemas/CampaignFiltersApplied'}, kpis: {type: object, properties: {campaigns_aggregate: {type: object, properties: {campaign_count: {type: integer, description: 'Number of campaigns in the last 30 days', example: 5}, total_emails_sent: {type: integer, description: 'Total number of emails sent in the last 30 days', example: 25000}, total_emails_delivered: {type: integer, description: 'Total number of emails delivered in the last 30 days', example: 23750}, average_open_rate: {type: number, format: float, description: 'Average open rate in the last 30 days as percentage (0-100)', example: 32.5}, average_click_rate: {type: number, format: float, description: 'Average click rate in the last 30 days as percentage (0-100)', example: 6.8}, campaign_count_diff: {type: number, format: float, description: 'Percentage difference in campaign count vs previous 3 months average', example: 15}, total_emails_sent_diff: {type: number, format: float, description: 'Percentage difference in emails sent vs previous 3 months average', example: 8.5}, total_emails_delivered_diff: {type: number, format: float, description: 'Percentage difference in emails delivered vs previous 3 months average', example: 9.2}, average_open_rate_diff: {type: number, format: float, description: 'Percentage difference in open rate vs previous 3 months average', example: -2.1}, average_click_rate_diff: {type: number, format: float, description: 'Percentage difference in click rate vs previous 3 months average', example: 1.3}}}}}}}}}]}, examples: {campaignsAggregateBasicFilters: {summary: 'Example response with basic filters (goal and status)', value: {status: success, data: {group_id: 1, filters: {goal: announcement, status: started}, kpis: {campaigns_aggregate: {campaign_count: 5, total_emails_sent: 25000, total_emails_delivered: 23750, average_open_rate: 32.5, average_click_rate: 6.8, campaign_count_diff: 15, total_emails_sent_diff: 8.5, total_emails_delivered_diff: 9.2, average_open_rate_diff: -2.1, average_click_rate_diff: 1.3}}}}}, campaignsAggregateComplexFilters: {summary: 'Example response with complex filters including date ranges, lists, and performance criteria', value: {status: success, data: {group_id: 1, filters: {goal: newsletter, status: started, campaign_name: weekly, started_at: {from: '2024-01-01T00:00:00Z', to: '2024-12-31T23:59:59Z'}, contacts_selected: {min: 1000, max: 50000}, performance_rating: {min: 0.8}, sender_ids: [123, 456, 789], user_ids: [100, 200], template_ids: [10, 20, 30], is_successful: true}, kpis: {campaigns_aggregate: {campaign_count: 3, total_emails_sent: 18500, total_emails_delivered: 18200, average_open_rate: 38.2, average_click_rate: 8.4, campaign_count_diff: 50, total_emails_sent_diff: 22.3, total_emails_delivered_diff: 24.1, average_open_rate_diff: 5.7, average_click_rate_diff: 3.2}}}}}, campaignsAggregateNoFilters: {summary: 'Example response with no filters applied', value: {status: success, data: {group_id: 1, filters: {}, kpis: {campaigns_aggregate: {campaign_count: 12, total_emails_sent: 58000, total_emails_delivered: 55100, average_open_rate: 28.7, average_click_rate: 5.2, campaign_count_diff: 25.5, total_emails_sent_diff: 12.3, total_emails_delivered_diff: 14.8, average_open_rate_diff: 3.4, average_click_rate_diff: -1.8}}}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /v2/kpi/emailer/campaigns/batch/preview:
        get:
            tags: [Campaign]
            summary: 'Get basic statistics for multiple campaigns (batch operation)'
            description: "Retrieves essential campaign statistics (open_rate, click_rate) for multiple campaigns in a single request.\nThis endpoint is optimized for performance and only returns core metrics without detailed comparisons.\n\n**Key Features:**\n- Fast batch processing of up to 100 campaigns\n- Returns only essential metrics (delivered_count, open_count, open_rate, total_open_count, click_count, click_rate, total_click_count)\n- Optimized database queries for better performance\n- Identifies campaigns not found in the specified group\n\n**Use Cases:**\n- Dashboard overviews requiring basic stats for multiple campaigns\n- Bulk operations needing quick performance indicators\n- Reports focusing on core email engagement metrics"
            parameters: [{name: campaign_ids, in: query, description: 'Comma-separated list of campaign IDs to get statistics for (maximum 100)', required: true, schema: {type: string, pattern: '^[0-9]+(,[0-9]+)*$', example: '123,456,789,1001,1002'}, example: '123,456,789'}]
            responses: {'200': {description: 'Batch campaign basic statistics retrieved successfully', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, description: 'Response containing basic statistics for multiple campaigns', properties: {group_id: {type: integer, description: 'ID of the group the campaigns belong to', example: 1}, stats: {type: array, description: 'Array of campaign statistics', items: {$ref: '#/components/schemas/CampaignBasicStats'}}, not_found: {type: array, description: 'Array of campaign IDs that were not found in the specified group', items: {type: integer}, example: [999, 1000], nullable: true}}}}}]}, examples: {success: {summary: 'Example response for successful batch campaign basic stats (all campaigns found)', value: {status: success, data: {group_id: 1, stats: [{campaign_id: 123, delivered_count: 950, open_count: 350, open_rate: 35, total_open_count: 470, click_count: 50, click_rate: 5, total_click_count: 58, performance_rating: 85.2}, {campaign_id: 456, delivered_count: 1900, open_count: 600, open_rate: 30, total_open_count: 810, click_count: 80, click_rate: 4, total_click_count: 95, performance_rating: 78.4}, {campaign_id: 789, delivered_count: 1450, open_count: 450, open_rate: 30, total_open_count: 605, click_count: 75, click_rate: 5, total_click_count: 88, performance_rating: 80}]}}}, partialSuccess: {summary: 'Example response when some campaigns are not found (replication lag scenario)', value: {status: success, data: {group_id: 1, stats: [{campaign_id: 123, delivered_count: 950, open_count: 350, open_rate: 35, total_open_count: 470, click_count: 50, click_rate: 5, total_click_count: 58, performance_rating: 85.2}, {campaign_id: 456, delivered_count: 1900, open_count: 600, open_rate: 30, total_open_count: 810, click_count: 80, click_rate: 4, total_click_count: 95, performance_rating: 78.4}], not_found: [789, 101112]}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/campaign:
        post:
            tags: [Campaign]
            summary: 'Create a new campaign'
            description: 'Creates a new campaign with the provided details.'
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [data], properties: {data: {type: object, required: [campaign_name], properties: {campaign_name: {type: string, description: 'Name of the campaign'}, subject: {type: string, description: 'Subject line of the campaign'}, sender_id: {type: integer, description: 'ID of the sender'}, reply_to: {type: string, description: 'Reply-to email address'}, goal: {type: string, description: 'Campaign goal'}, saved_filter_id: {type: integer, description: 'ID of the saved filter to use'}, emailer_template_id: {type: integer, description: 'ID of the email template to use'}, scheduled_at: {type: string, format: date-time, description: 'When to schedule the campaign (UTC)'}, is_successful: {type: boolean, description: 'Whether the campaign was successful (for performance tracking)', example: true}}}}}}}}
            responses: {'200': {description: 'Campaign created successfully', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the campaign'}, goal: {type: string, description: 'Campaign goal'}, campaign_name: {type: string, description: 'Name of the campaign'}, sender: {$ref: '#/components/schemas/EmailerSender'}, template: {$ref: '#/components/schemas/EmailerTemplate'}, saved_filter: {$ref: '#/components/schemas/SavedFilter'}, user_id: {type: integer, description: 'ID of the user who created the campaign'}, reply_to: {type: string, description: 'Reply-to email address'}, tested: {type: boolean, nullable: true, description: 'Whether the campaign has been tested'}, group_id: {type: integer, description: 'ID of the group the campaign belongs to'}, created_at: {type: string, format: date-time, description: 'When the campaign was created'}, updated_at: {type: string, format: date-time, description: 'When the campaign was last updated'}, provider: {type: string, description: 'Email provider used for the campaign'}, subject: {type: string, description: 'Subject line of the campaign'}, status: {type: string, enum: [draft, pending, scheduled, running, rejected, cancelled, started, archived, failed], description: 'Current status of the campaign'}, contacts_selected: {type: integer, description: 'Number of contacts selected for the campaign'}, scheduled_at: {type: string, format: date-time, nullable: true, description: 'When the campaign is scheduled to be sent'}, started_at: {type: string, format: date-time, nullable: true, description: 'When the campaign was started'}, state: {$ref: '#/components/schemas/EmailerCampaignState'}, steps: {type: array, items: {$ref: '#/components/schemas/EmailerCampaignStep'}}, stats: {$ref: '#/components/schemas/EmailerCampaignStats'}, is_successful: {type: boolean, nullable: true, description: 'Whether the campaign was successful (for performance tracking)'}, delivered_count: {type: integer, nullable: true, description: 'Number of emails delivered (Only present for archived campaigns)'}, open_count: {type: integer, nullable: true, description: 'Number of emails opened (Only present for archived campaigns)'}, unique_click_count: {type: integer, nullable: true, description: 'Number of unique clicks (Only present for archived campaigns)'}, hard_bounce_count: {type: integer, nullable: true, description: 'Number of hard bounces (Only present for archived campaigns)'}, unsubscribe_count: {type: integer, nullable: true, description: 'Number of unsubscribes (Only present for archived campaigns)'}, spam_complaints_count: {type: integer, nullable: true, description: 'Number of spam complaints (Only present for archived campaigns)'}}}}}]}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
        patch:
            tags: [Campaign]
            summary: 'Update a campaign'
            description: 'Updates an existing campaign with new details.'
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [data], properties: {data: {type: object, properties: {id: {type: integer, description: 'Campaign ID'}, campaign_name: {type: string, description: 'Name of the campaign'}, subject: {type: string, description: 'Subject line of the campaign'}, sender_id: {type: integer, description: 'ID of the sender'}, reply_to: {type: string, description: 'Reply-to email address'}, goal: {type: string, description: 'Campaign goal'}, saved_filter_id: {type: integer, description: 'ID of the saved filter to use'}, emailer_template_id: {type: integer, description: 'ID of the email template to use'}, scheduled_at: {type: string, format: date-time, description: 'When to schedule the campaign (UTC)'}, is_successful: {type: boolean, description: 'Whether the campaign was successful (for performance tracking)', example: true}}}}}}}}
            responses: {'200': {description: 'Campaign updated successfully', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the campaign'}, goal: {type: string, description: 'Campaign goal'}, campaign_name: {type: string, description: 'Name of the campaign'}, sender: {$ref: '#/components/schemas/EmailerSender'}, template: {$ref: '#/components/schemas/EmailerTemplate'}, saved_filter: {$ref: '#/components/schemas/SavedFilter'}, user_id: {type: integer, description: 'ID of the user who created the campaign'}, reply_to: {type: string, description: 'Reply-to email address'}, tested: {type: boolean, nullable: true, description: 'Whether the campaign has been tested'}, group_id: {type: integer, description: 'ID of the group the campaign belongs to'}, created_at: {type: string, format: date-time, description: 'When the campaign was created'}, updated_at: {type: string, format: date-time, description: 'When the campaign was last updated'}, provider: {type: string, description: 'Email provider used for the campaign'}, subject: {type: string, description: 'Subject line of the campaign'}, status: {type: string, enum: [draft, pending, scheduled, running, rejected, cancelled, started, archived, failed], description: 'Current status of the campaign'}, contacts_selected: {type: integer, description: 'Number of contacts selected for the campaign'}, scheduled_at: {type: string, format: date-time, nullable: true, description: 'When the campaign is scheduled to be sent'}, started_at: {type: string, format: date-time, nullable: true, description: 'When the campaign was started'}, state: {$ref: '#/components/schemas/EmailerCampaignState'}, steps: {type: array, items: {$ref: '#/components/schemas/EmailerCampaignStep'}}, stats: {$ref: '#/components/schemas/EmailerCampaignStats'}, is_successful: {type: boolean, nullable: true, description: 'Whether the campaign was successful (for performance tracking)'}, delivered_count: {type: integer, nullable: true, description: 'Number of emails delivered (Only present for archived campaigns)'}, open_count: {type: integer, nullable: true, description: 'Number of emails opened (Only present for archived campaigns)'}, unique_click_count: {type: integer, nullable: true, description: 'Number of unique clicks (Only present for archived campaigns)'}, hard_bounce_count: {type: integer, nullable: true, description: 'Number of hard bounces (Only present for archived campaigns)'}, unsubscribe_count: {type: integer, nullable: true, description: 'Number of unsubscribes (Only present for archived campaigns)'}, spam_complaints_count: {type: integer, nullable: true, description: 'Number of spam complaints (Only present for archived campaigns)'}}}}}]}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/campaigns/search:
        post:
            tags: [Campaign]
            summary: 'Search campaigns with enhanced filtering'
            description: "Searches for campaigns based on comprehensive filtering criteria with enhanced capabilities.\n\n**Enhanced Filter Types:**\n- **Basic Filters**: status, subject, campaign_name, goal, reply_to\n- **Boolean Filters**: is_successful\n- **Date Range Filters**: started_at, created_at (supports from/to)\n- **Number Range Filters**: contacts_selected, performance_rating (supports min/max)\n- **List Filters**: sender_ids, user_ids, saved_filter_ids, template_ids (arrays)\n- **Legacy Single ID Filters**: sender_id, saved_filter_id, emailer_template_id (backward compatibility)\n\n**Pagination**: Supports page and per_page parameters for result pagination."
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [data], properties: {data: {type: object, properties: {status: {type: string, enum: [draft, pending, scheduled, running, rejected, cancelled, started, archived, failed], description: 'Filter by campaign status'}, subject: {type: string, description: 'Filter by subject (partial match)'}, reply_to: {type: string, description: 'Filter by reply-to address'}, campaign_name: {type: string, description: 'Filter by campaign name (partial match)'}, goal: {type: string, description: 'Filter by campaign goal'}, group_id: {type: integer, description: 'Filter by group ID'}, user_id: {type: integer, description: 'Filter by user ID'}, page: {type: integer, default: 1, description: 'Page number for pagination'}, per_page: {type: integer, default: 50, description: 'Number of items per page'}, is_successful: {type: boolean, description: 'Filter by campaign success status', example: true}, started_at_from: {type: string, format: date-time, description: 'Filter campaigns started from this date (inclusive)', example: '2024-01-01T00:00:00Z'}, started_at_to: {type: string, format: date-time, description: 'Filter campaigns started until this date (inclusive)', example: '2024-12-31T23:59:59Z'}, created_at_from: {type: string, format: date-time, description: 'Filter campaigns created from this date (inclusive)', example: '2024-01-01T00:00:00Z'}, created_at_to: {type: string, format: date-time, description: 'Filter campaigns created until this date (inclusive)', example: '2024-12-31T23:59:59Z'}, contacts_selected_min: {type: integer, minimum: 0, description: 'Minimum number of contacts selected', example: 100}, contacts_selected_max: {type: integer, minimum: 1, description: 'Maximum number of contacts selected', example: 10000}, performance_rating_min: {type: number, format: float, minimum: 0, maximum: 1, description: 'Minimum performance rating (0.0-1.0)', example: 0.7}, performance_rating_max: {type: number, format: float, minimum: 0, maximum: 1, description: 'Maximum performance rating (0.0-1.0)', example: 1}, sender_ids: {type: array, items: {type: integer}, description: 'List of sender IDs to filter by', example: [123, 456, 789]}, user_ids: {type: array, items: {type: integer}, description: 'List of user IDs to filter by', example: [100, 200, 300]}, saved_filter_ids: {type: array, items: {type: integer}, description: 'List of saved filter IDs to filter by', example: [50, 60, 70]}, template_ids: {type: array, items: {type: integer}, description: 'List of template IDs to filter by (replaces emailer_template_id)', example: [10, 20, 30]}, sender_id: {type: integer, description: 'Filter by sender ID (deprecated - use sender_ids instead)'}, saved_filter_id: {type: integer, description: 'Filter by saved filter ID (deprecated - use saved_filter_ids instead)'}, emailer_template_id: {type: integer, description: 'Filter by template ID (deprecated - use template_ids instead)'}, scheduled_at: {type: string, format: date-time, description: 'Filter by scheduled date (legacy - use started_at_from/to for date ranges)'}}}}}, examples: {basicSearch: {summary: 'Basic search with status and goal filters', value: {data: {status: started, goal: newsletter, page: 0, per_page: 20}}}, enhancedSearch: {summary: 'Enhanced search with date ranges, lists, and performance criteria', value: {data: {status: started, campaign_name: weekly, started_at_from: '2024-01-01T00:00:00Z', started_at_to: '2024-12-31T23:59:59Z', contacts_selected_min: 1000, contacts_selected_max: 50000, performance_rating_min: 0.8, sender_ids: [123, 456, 789], user_ids: [100, 200], template_ids: [10, 20, 30], is_successful: true, page: 0, per_page: 50}}}, legacyCompatibility: {summary: 'Legacy single ID filters (backward compatibility)', value: {data: {status: draft, sender_id: 123, saved_filter_id: 50, emailer_template_id: 10, scheduled_at: '2024-12-25T10:00:00Z', page: 0, per_page: 25}}}}}}}
            responses: {'200': {description: 'Campaigns retrieved successfully', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, properties: {campaigns: {type: array, items: {$ref: '#/components/schemas/EmailerCampaignView'}}, total: {type: integer, description: 'Total number of campaigns in current page'}, page: {type: integer, description: 'Current page number'}, per_page: {type: integer, description: 'Number of campaigns per page'}, all_count: {type: integer, description: 'Total number of campaigns across all pages'}}}]}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/campaigns/list:
        get:
            tags: [Campaign]
            summary: 'Get list of campaigns'
            description: "Retrieves a simple list of campaigns that are either started or archived.\nThis endpoint returns basic campaign information without detailed statistics or relationships.\nThe response is optimized for performance and does not include computed fields like open rates or click rates."
            responses: {'200': {description: 'List of campaigns retrieved successfully', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, properties: {campaigns: {type: array, items: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the campaign', example: 198}, campaign_name: {type: string, description: 'Name of the campaign', example: 'Monthly Newsletter December 2024'}, status: {type: string, enum: [draft, pending, scheduled, started, archived, failed, cancelled], description: 'Current status of the campaign', example: started}, created_at: {type: string, format: date-time, description: 'When the campaign was created', example: '2024-12-01T10:00:00.000Z'}, updated_at: {type: string, format: date-time, description: 'When the campaign was last updated', example: '2024-12-01T15:30:00.000Z'}, user_id: {type: integer, description: 'ID of the user who created the campaign', example: 42}, started_at: {type: string, format: date-time, nullable: true, description: 'When the campaign was started', example: '2024-12-01T12:00:00.000Z'}, goal: {type: string, description: 'Campaign goal', example: Newsletter}}}}, total: {type: integer, description: 'Total number of campaigns in the list', example: 5}}}}}]}, examples: {campaignsList: {summary: 'Example response for campaigns list', value: {status: success, data: {campaigns: [{id: 198, campaign_name: 'Monthly Newsletter December 2024', status: started, created_at: '2024-12-01T10:00:00.000Z', updated_at: '2024-12-01T15:30:00.000Z', user_id: 42, started_at: '2024-12-01T12:00:00.000Z', goal: Newsletter}, {id: 199, campaign_name: 'Product Announcement', status: archived, created_at: '2024-11-15T09:00:00.000Z', updated_at: '2024-11-20T14:00:00.000Z', user_id: 42, started_at: '2024-11-16T10:00:00.000Z', goal: Announcement}], total: 2}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    '/emailer/campaign/{campaignId}/report':
        get:
            tags: [Campaign]
            summary: 'Get campaign report'
            description: 'Retrieves detailed statistics and metrics for a campaign.'
            parameters: [{name: campaignId, in: path, description: 'ID of the campaign to get report for', required: true, schema: {type: integer}}]
            responses: {'200': {description: 'Campaign report retrieved successfully', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, properties: {unique_clicks: {type: integer, description: 'Number of unique clicks'}, clickers: {type: integer, description: 'Number of unique clickers'}, complaints: {type: integer, description: 'Number of complaints/abuse reports'}, delivered: {type: integer, description: 'Number of emails delivered'}, sent: {type: integer, description: 'Number of emails sent'}, soft_bounces: {type: integer, description: 'Number of soft bounces'}, hard_bounces: {type: integer, description: 'Number of hard bounces'}, unique_views: {type: integer, description: 'Number of unique views'}, trackable_views: {type: integer, description: 'Number of trackable views'}, trackable_views_rate: {type: number, description: 'Trackable views rate as decimal'}, estimated_views: {type: integer, description: 'Estimated number of views'}, unsubscriptions: {type: integer, description: 'Number of unsubscriptions'}, viewed: {type: integer, description: 'Number of emails viewed'}, deferred: {type: integer, description: 'Number of emails deferred'}, return_bounce: {type: integer, description: 'Number of return bounces'}}}}}]}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/campaign/send:
        post:
            tags: [Campaign]
            summary: 'Send a campaign'
            description: "Initiates sending of a campaign to its recipients. This endpoint returns immediately with HTTP 202 Accepted\nto prevent timeout issues while the campaign is being processed asynchronously in the background.\n\n**Processing Flow:**\n1. **Immediate Response**: Returns HTTP 202 with basic campaign info\n2. **Background Processing**: Campaign validation, contact retrieval, and sending happens asynchronously\n3. **Status Tracking**: Use campaign status and steps to monitor progress\n\n**Campaign Types:**\n- **Test Campaign**: When `emails` array is provided, sends to specified test addresses\n- **Real Campaign**: When `emails` is empty/null, sends to campaign's saved filter audience\n\n**Status Progression:**\n- `pending` → Campaign processing initiated\n- `scheduled` → Campaign queued for sending  \n- `running` → Provider actively sending emails\n- `failed` → Error occurred during processing\n- `cancelled` → Campaign cancelled by user or system\n\n**Error Handling:**\n- Validation errors are tracked in campaign steps (see `campaign_verification` step)\n- Failed campaigns will have status `failed` with error details in steps\n- Use GET `/emailer/campaign/{campaignId}` to check current status and steps\n- Each step's `error` field carries a machine-readable code. The stored string is wrapped\n  (`__STATUSERROR_V1__:status <http>: <code>`); classify on the embedded `<code>`.\n\n**Error codes (Brevo)**\n\nVerification — recorded on step `campaign_verification` (order 0):\n\n| Code | Trigger |\n|------|---------|\n| `campaign_id_required` | Missing campaign id |\n| `group_id_required` | Missing group id |\n| `mailjet_api_keys_missing` | Group has no Brevo public/secret key |\n| `get_campaign_error` | Campaign row failed to load |\n| `campaign_not_in_right_status` | Campaign not in `draft`/`failed` |\n| `template_not_set` | No template selected |\n| `sender_not_set` | No sender selected |\n| `saved_filter_not_set` / `audience_not_set` | No audience / saved filter |\n| `verify_campaign_error` | Provider verification failed |\n| `subject_missing` | Subject empty |\n| `invalid_reply_to` | Reply-to invalid (`invalid_email_structure`, `invalid_email_domain`) |\n| `scheduled_at_in_the_past` | Schedule too far in the past |\n| `update_campaign_status_error` | Failed to set status `pending` |\n| `retrieve_contacts_error` | Contact retrieval/filtering failed |\n| `realize_template_error` | Per-contact render failed (`object or subject is required`, `problem formatting fields in subject`, `problem formatting fields in object`, `failed to render template: …`) |\n\nSend — recorded per step:\n\n| Order / step | Codes |\n|--------------|-------|\n| 1 `format_template_with_attributes` | `realize_template_error`; `Failed to retrieve attributes`; `Failed to create GENDER attribute`; `Failed to create MARITAL_NAME attribute`; `Failed to create QOMON_ID attribute` |\n| 2 `qomon_audiences_brevo_folder` | `brevo_api_error_code_<CODE>`; `brevo_api_error` |\n| 3 `qomon_webhook` (real send only) | `brevo_api_error_code_<CODE>`; `brevo_api_error` |\n| 4 `create_brevo_list_into_qomon_audiences_folder` | `brevo_api_error_code_<CODE>`; `brevo_api_error` |\n| 5 `import_contacts_into_brevo_list` | `brevo_api_error_code_<CODE>`; `brevo_api_error`; `import process took too long for process: <ID>` |\n| 6 `create_brevo_campaign` | `marshal_campaign_tag_error`; `scheduled_at_in_the_past`; `brevo_api_error_code_<CODE>`; `brevo_api_error` |\n| 7 `linking_emails_to_campaign` | `SaveEmails: no emails to save` |\n| 8 `start_brevo_campaign` | `brevo_api_error_code_<CODE>`; `brevo_api_error` |\n\n`<CODE>` is Brevo's `ErrorModel.Code` (e.g. `invalid_parameter`, `duplicate_parameter`, `unauthorized`,\n`not_enough_credits`). `brevo_api_error` is the fallback when Brevo returns no structured error;\n`api_error_<context>` covers non-HTTP failures (timeouts, etc.). Persistence-infra codes\n(`update_campaign_step_error`, `update_campaign_error`, `get_attributes_error`) may appear on any step."
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [data], properties: {data: {type: object, required: [campaign_id], properties: {campaign_id: {type: integer, description: 'ID of the campaign to send', example: 198}, emails: {type: array, items: {type: string, format: email}, description: "List of test email addresses. If provided, campaign will be sent as a test \nto these addresses only. If empty/null, sends to campaign's saved filter audience.", example: [test@example.com, admin@company.com]}, scheduled_at: {type: string, format: date-time, description: "Optional scheduled time for campaign sending (UTC). Only applies to real campaigns,\nignored for test campaigns.", example: '2024-12-25T10:00:00.000Z'}}}}}, examples: {testCampaign: {summary: 'Send test campaign to specific email addresses', value: {data: {campaign_id: 198, emails: [test@example.com, admin@company.com]}}}, realCampaign: {summary: 'Send real campaign to saved filter audience', value: {data: {campaign_id: 198}}}, scheduledCampaign: {summary: 'Schedule real campaign for future sending', value: {data: {campaign_id: 198, scheduled_at: '2024-12-25T10:00:00.000Z'}}}}}}}
            responses: {'202': {description: "Campaign sending initiated successfully. The request has been accepted for processing\nand will continue asynchronously in the background. The response contains initial campaign\ninformation and tracking links. Use the campaign status and steps to monitor progress.\n\n**Response Details:**\n- `test`: Boolean indicating if this is a test campaign\n- `steps`: Campaign processing steps (initially empty, populated during processing)\n- `filter`: Contact filtering results (populated after contact retrieval completes)\n- `in_progress`: Queue information for ElasticEmail campaigns (if applicable)\n- `links`: HATEOAS links to monitor campaign progress", content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, description: "Response for campaign send operations. Returns immediately with HTTP 202 Accepted\nfor asynchronous processing. Some fields are populated immediately while others\nare populated during background processing.", properties: {steps: {type: array, items: {$ref: '#/components/schemas/EmailerCampaignStep'}, description: "Campaign processing steps for tracking progress. Initially empty in the immediate\nresponse, populated during background processing. Check campaign status for updates."}, filter: {$ref: '#/components/schemas/CampaignContactFilter'}, in_progress: {$ref: '#/components/schemas/InProgress'}, links: {type: array, items: {$ref: '#/components/schemas/CampaignStepLink'}, description: "HATEOAS links for related resources. Always includes a link to the campaign\ndetails endpoint for monitoring progress."}, test: {type: boolean, description: "Indicates if this is a test campaign. True for campaigns sent to specific\nemail addresses, false for campaigns sent to saved filter audiences.", example: false}}}}}]}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/campaign/realize:
        post:
            tags: [Campaign]
            summary: 'Realize a campaign template'
            description: "Renders a campaign template, translating Qomon's own merge tags (e.g. {{ firstname }},\n{{ salutation }}) into their Brevo-native form (e.g. {{contact.FIRSTNAME}},\n{{contact.CIVILITE}}). This is NOT a live preview with real data - a campaign is rendered\nonce for the whole recipient batch, so the actual per-recipient value is filled in by Brevo\nitself at send time, not here. For a preview with real data, use POST /emailer/template/realize.\n\nSee GET /emailer/template/variables (optionally `?template_type=campaign`) for which merge\ntags are valid to use here."
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [data], properties: {data: {type: object, description: "Realizes a campaign template: substitutes Qomon's own merge tags (e.g.\n{{ firstname }}, {{ salutation }}) with their Brevo-native form (e.g.\n{{contact.FIRSTNAME}}, {{contact.CIVILITE}}) - it does NOT substitute real\nper-recipient data, since a campaign is rendered once for the whole\nbatch and Brevo resolves the actual value per recipient at send time.\nFor transactional templates rendered with real data instead, use\nPOST /emailer/template/realize.", properties: {object: {type: string, description: 'Raw HTML template content to realize. Required unless emailer_template_id is provided.'}, subject: {type: string, description: 'Email subject line (also realized - merge tags substituted the same way as `object`).'}, emailer_template_id: {type: integer, description: 'ID of an existing email template to load and realize, instead of passing raw `object`/`subject`.'}, locale: {type: string, description: "Locale used to pick the right Brevo attribute names (e.g. GENRE/CIVILITE for \"fr\" vs\nGENDER/SALUTATION otherwise) and unsubscribe wording. Auto-resolved from the group's\nsettings if omitted."}, provider: {type: string, description: 'Email provider (e.g. "brevo"). Auto-resolved from the group''s configured provider if omitted.'}, campaign_id: {type: integer, description: 'Campaign ID. Used to build the per-campaign topic-unsubscribe link ({{altunsubscribe}}).'}, campaign_attributes: {$ref: '#/components/schemas/CampaignAttributes'}}}}}}}}
            responses: {'200': {description: 'Campaign template successfully realized.', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, properties: {email: {type: object, properties: {subject: {type: string, description: 'Realized subject line.'}, object: {type: string, description: 'Realized HTML body, containing Brevo-native merge tags (e.g. {{contact.FIRSTNAME}}) ready to send to Brevo.'}, provider: {type: string, example: brevo}}}}}}}]}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    '/emailer/campaign/{campaignId}/follow-up':
        post:
            tags: [Campaign]
            summary: 'Create a follow-up campaign'
            description: "Creates a follow-up campaign from an existing started campaign. This endpoint allows you to quickly \ncreate a new draft campaign based on a previously sent campaign, maintaining consistency in messaging \nand audience targeting.\n\n**Requirements:**\n- The original campaign must have status `started` (campaigns that have been sent)\n- User must have access to the original campaign\n\n**Copied Fields from Original Campaign:**\n- `subject` - Email subject line\n- `goal` - Campaign goal/purpose\n- `reply_to` - Reply-to email address\n- `sender_id` - Email sender\n- `saved_filter_id` - Target audience filter (can be overridden with request body parameter)\n- `emailer_template_id` - Email template\n\n**Optional Override:**\n- You can provide a different `saved_filter_id` in the request body to target a different audience\n- The provided saved filter will be verified before being used\n- If not provided, the original campaign's saved filter will be used\n\n**New Campaign Properties:**\n- `campaign_name` - Original name with locale-aware \"Follow-up\" prefix\n  - English: \"Follow-up: [Original Name]\"\n  - French: \"Suivi : [Original Name]\"\n  - Spanish: \"Seguimiento: [Original Name]\"\n  - German: \"Nachverfolgung: [Original Name]\"\n  - Portuguese: \"Acompanhamento: [Original Name]\"\n  - Italian: \"Follow-up: [Original Name]\"\n- `status` - Set to `draft` (ready for editing before sending)\n- `user_id` - Set to the current user creating the follow-up\n- All other fields reset to defaults (scheduled_at, started_at, etc.)\n\n**Use Cases:**\n- Send reminders to the same audience\n- Follow up on previous announcements with a different audience segment\n- Re-engage with similar content to a refined audience\n- A/B test variations of successful campaigns with different targeting"
            parameters: [{name: campaignId, in: path, description: 'The unique identifier of the original campaign to create a follow-up from', required: true, example: 198, schema: {type: integer}}]
            requestBody: {required: false, content: {application/json: {schema: {type: object, properties: {data: {type: object, properties: {saved_filter_id: {type: integer, description: "Optional. Override the original campaign's audience with a different saved filter.\nIf provided, this saved filter will be verified and used instead of the original.\nIf omitted, the original campaign's saved_filter_id will be used.", example: 850}}}}}, examples: {withOverriddenAudience: {summary: 'Create follow-up with different audience', value: {data: {saved_filter_id: 850}}}, withoutOverride: {summary: 'Create follow-up with original audience (empty body)', value: {}}}}}}
            responses: {'200': {description: 'Follow-up campaign created successfully', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the campaign'}, goal: {type: string, description: 'Campaign goal'}, campaign_name: {type: string, description: 'Name of the campaign'}, sender: {$ref: '#/components/schemas/EmailerSender'}, template: {$ref: '#/components/schemas/EmailerTemplate'}, saved_filter: {$ref: '#/components/schemas/SavedFilter'}, user_id: {type: integer, description: 'ID of the user who created the campaign'}, reply_to: {type: string, description: 'Reply-to email address'}, tested: {type: boolean, nullable: true, description: 'Whether the campaign has been tested'}, group_id: {type: integer, description: 'ID of the group the campaign belongs to'}, created_at: {type: string, format: date-time, description: 'When the campaign was created'}, updated_at: {type: string, format: date-time, description: 'When the campaign was last updated'}, provider: {type: string, description: 'Email provider used for the campaign'}, subject: {type: string, description: 'Subject line of the campaign'}, status: {type: string, enum: [draft, pending, scheduled, running, rejected, cancelled, started, archived, failed], description: 'Current status of the campaign'}, contacts_selected: {type: integer, description: 'Number of contacts selected for the campaign'}, scheduled_at: {type: string, format: date-time, nullable: true, description: 'When the campaign is scheduled to be sent'}, started_at: {type: string, format: date-time, nullable: true, description: 'When the campaign was started'}, state: {$ref: '#/components/schemas/EmailerCampaignState'}, steps: {type: array, items: {$ref: '#/components/schemas/EmailerCampaignStep'}}, stats: {$ref: '#/components/schemas/EmailerCampaignStats'}, is_successful: {type: boolean, nullable: true, description: 'Whether the campaign was successful (for performance tracking)'}, delivered_count: {type: integer, nullable: true, description: 'Number of emails delivered (Only present for archived campaigns)'}, open_count: {type: integer, nullable: true, description: 'Number of emails opened (Only present for archived campaigns)'}, unique_click_count: {type: integer, nullable: true, description: 'Number of unique clicks (Only present for archived campaigns)'}, hard_bounce_count: {type: integer, nullable: true, description: 'Number of hard bounces (Only present for archived campaigns)'}, unsubscribe_count: {type: integer, nullable: true, description: 'Number of unsubscribes (Only present for archived campaigns)'}, spam_complaints_count: {type: integer, nullable: true, description: 'Number of spam complaints (Only present for archived campaigns)'}}}}}]}, examples: {followUpCampaignCreated: {summary: 'Example response for successfully created follow-up campaign', value: {status: success, data: {id: 199, goal: announcement, campaign_name: 'Follow-up: Product Launch Campaign', sender: {id: 123, email: sender@qomon.com, name: 'Marketing Team', validated: true}, template: {id: 456, name: 'Product Launch Template', content: '<!DOCTYPE html><html><body><h1>New Product Launch</h1></body></html>'}, saved_filter: {id: 789, title: 'Product Launch Audience', payload: '{"query":{"$all":[]}}'}, user_id: 42, reply_to: marketing@qomon.com, group_id: 1, status: draft, subject: 'Exciting Product Update!', contacts_selected: 5000, created_at: '2024-01-20T10:00:00Z', updated_at: '2024-01-20T10:00:00Z', state: {sender_set: true, audience_set: true, template_set: true, subject_set: true, progress: 1}}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: "Campaign cannot be followed up. This occurs when:\n- The original campaign status is not `started`\n- User doesn't have permission to access the campaign", content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [error, fail]}}}, {type: object, properties: {error: {type: string, example: campaign_must_be_started_to_follow_up}}}]}, examples: {campaignNotStarted: {summary: 'Original campaign is not in started status', value: {status: error, error: campaign_must_be_started_to_follow_up, message: 'Only campaigns with status ''started'' can be used to create follow-ups'}}}}}}, '404': {description: 'Original campaign not found', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [error, fail]}}}, {type: object, properties: {error: {type: string, example: campaign_not_found}}}]}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/brevo/rss:
        get:
            tags: [Campaign]
            summary: 'Get Brevo RSS feed'
            description: "Fetches the RSS feed from Brevo's status page (https://status.brevo.com/feed.rss) and returns it as a JSON response.\nThis endpoint provides real-time status information about Brevo's services and any ongoing issues or maintenance."
            responses: {'200': {description: 'RSS feed retrieved successfully', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, required: [rss], properties: {rss: {type: string, description: 'The RSS feed content as a string', example: '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">...'}}}}}]}, examples: {brevoRssResponse: {summary: 'Example response for Brevo RSS feed', value: {status: success, data: {rss: '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>Brevo Status</title><description>Real-time status of Brevo services</description><item><title>All Systems Operational</title><description>All services are running normally</description></item></channel></rss>'}}}}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/invalid-contacts:
        get:
            tags: [Campaign]
            summary: 'Get contacts excluded from campaign sends'
            description: "Retrieves, for the authenticated group, every contact that was excluded from a\ncampaign send: invalid email format/MX rejects (tracked since the last reset)\nmerged with hard-bounce contacts recorded in the group's email event history.\n\nIntended for building a CSV export: `contact_id`, `email`, `reason`, `description`.\nRows are sorted by `email` ascending. The same contact/email may appear more than\nonce if it recurred across sends - call `POST /emailer/invalid-contacts/reset` to\nclear the format/mx entries."
            responses: {'200': {description: 'Invalid contacts retrieved successfully', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, properties: {rows: {type: array, items: {$ref: '#/components/schemas/InvalidContactRow'}}}}}}]}, examples: {invalidContactsReport: {summary: 'Example response listing excluded contacts', value: {status: success, data: {rows: [{contact_id: 4005595, email: not-an-email-format-test1, reason: format}, {contact_id: 3994019, email: test@nonexistent-test-domain.invalid, reason: mx}, {contact_id: 3991132, email: test@example.com, reason: hard_bounce, description: '550-5.1.1 The email account that you tried to reach does not exist.'}]}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/invalid-contacts/reset:
        post:
            tags: [Campaign]
            summary: 'Reset tracked invalid-contact rejects for the group'
            description: "Clears the format/mx rejects tracked for the authenticated group since the last\nreset. Hard-bounce data (from the group's email event history) is not affected -\nit reflects real delivery history, not something this reset is meant to erase."
            responses: {'200': {description: 'Invalid contacts reset successfully', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, properties: {success: {type: boolean, example: true}}}}}]}, examples: {invalidContactsReset: {summary: 'Example response after resetting', value: {status: success, data: {success: true}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    '/emailer/template/{id}':
        get:
            tags: [Template]
            summary: 'Retrieve an email template by ID'
            description: "Fetches the metadata and content for a specific email template. This endpoint can retrieve \ntemplates of any category including 'workflow_message' templates (unlike GET /emailer/templates \nwhich excludes them).\n"
            parameters: [{name: id, in: path, required: true, description: 'Unique identifier of the email template.', schema: {type: integer}}]
            responses: {'200': {description: 'Successfully retrieved the email template.', content: {application/json: {schema: {type: object, required: [status, data], properties: {status: {type: string, enum: [success]}, data: {type: object, required: [emailer_template], properties: {emailer_template: {$ref: '#/components/schemas/EmailerTemplate'}}}}}, examples: {EmailerTemplateExample: {summary: 'Example response for a single email template', value: {status: success, data: {emailer_template: {id: 88888, group_id: 1, user_id: 42, CreatedAt: '2023-10-18T13:51:39.156291Z', UpdatedAt: '2024-11-19T12:56:27.505651Z', name: 'New test template updated', content_html: '<!DOCTYPE html><html><body><h1>Hello World</h1></body></html>', content_json: '{"page":{"body":{"container":{"style":{"background-color":"transparent"}}}}', image_file_id: 888888, image_url: 'https://file-qomon.s3.amazonaws.com/template_88888.jpeg', url: /emailer/template/88888, is_favorite: false, locale: en, category: email}}}}}}}}, '400': {description: 'Bad request due to invalid input.'}, '404': {description: 'Template not found.'}, '500': {description: 'Internal server error during template retrieval.'}}
        delete:
            tags: [Template]
            summary: 'Delete an email template'
            description: 'Deletes an existing email template by its ID.'
            parameters: [{name: id, in: path, required: true, description: 'Unique identifier of the email template to delete.', schema: {type: integer}}]
            responses: {'200': {description: 'A list of email templates retrieved successfully.', content: {application/json: {schema: {type: object, required: [status, data], properties: {status: {type: string, enum: [success]}, data: {type: object, required: [emailer_templates], properties: {emailer_templates: {type: array, items: {$ref: '#/components/schemas/EmailerTemplate'}}}}}}, examples: {EmailerTemplatesExample: {summary: 'Example response for multiple email templates', value: {status: success, data: {emailer_templates: [{id: 88888, group_id: 1, user_id: 42, CreatedAt: '2023-10-18T13:51:39.156291Z', UpdatedAt: '2024-11-19T12:56:27.505651Z', name: 'Newsletter Template', content_html: '<!DOCTYPE html><html><body><h1>Newsletter</h1></body></html>', image_file_id: 888888, image_url: 'https://file-qomon.s3.amazonaws.com/template_88888.jpeg', url: /emailer/template/88888, is_favorite: true, locale: en, category: email}, {id: 88889, group_id: 1, user_id: 42, CreatedAt: '2023-10-19T14:22:15.123456Z', UpdatedAt: '2024-11-20T09:15:33.789012Z', name: 'Event Invitation', content_html: '<!DOCTYPE html><html><body><h1>You''re Invited!</h1></body></html>', image_file_id: 888889, image_url: 'https://file-qomon.s3.amazonaws.com/template_88889.jpeg', url: /emailer/template/88889, is_favorite: false, locale: fr, category: email}]}}}}}}}, '404': {description: 'Template not found.'}, '500': {description: 'Internal server error during template deletion.'}}
    /emailer/templates:
        get:
            tags: [Template]
            summary: 'Retrieve a list of email templates'
            description: "Fetch metadata and content for available email templates. \nNote: Templates with category='workflow_message' are not returned by this endpoint \nas they are internal workflow message templates.\n"
            responses: {'200': {description: 'A list of email templates retrieved successfully.', content: {application/json: {schema: {type: object, required: [status, data], properties: {status: {type: string, enum: [success]}, data: {type: object, required: [emailer_templates], properties: {emailer_templates: {type: array, items: {$ref: '#/components/schemas/EmailerTemplate'}}}}}}, examples: {EmailerTemplatesExample: {summary: 'Example response for multiple email templates', value: {status: success, data: {emailer_templates: [{id: 88888, group_id: 1, user_id: 42, CreatedAt: '2023-10-18T13:51:39.156291Z', UpdatedAt: '2024-11-19T12:56:27.505651Z', name: 'Newsletter Template', content_html: '<!DOCTYPE html><html><body><h1>Newsletter</h1></body></html>', image_file_id: 888888, image_url: 'https://file-qomon.s3.amazonaws.com/template_88888.jpeg', url: /emailer/template/88888, is_favorite: true, locale: en, category: email}, {id: 88889, group_id: 1, user_id: 42, CreatedAt: '2023-10-19T14:22:15.123456Z', UpdatedAt: '2024-11-20T09:15:33.789012Z', name: 'Event Invitation', content_html: '<!DOCTYPE html><html><body><h1>You''re Invited!</h1></body></html>', image_file_id: 888889, image_url: 'https://file-qomon.s3.amazonaws.com/template_88889.jpeg', url: /emailer/template/88889, is_favorite: false, locale: fr, category: email}]}}}}}}}, '400': {description: 'Bad request due to invalid input.'}, '500': {description: 'Internal server error occurred during processing.'}}
    '/emailer/templates/user/{id}':
        get:
            tags: [Template]
            summary: 'Retrieve email templates for a specific user'
            description: 'Fetches all email templates created by a specific user within the selected group.'
            parameters: [{name: id, in: path, required: true, description: 'Unique identifier of the user.', schema: {type: integer}}]
            responses: {'200': {description: 'Successfully retrieved the email templates for the user.', content: {application/json: {schema: {type: object, required: [status, data], properties: {status: {type: string, enum: [success]}, data: {type: object, required: [emailer_templates], properties: {emailer_templates: {type: array, items: {$ref: '#/components/schemas/EmailerTemplate'}}}}}}, examples: {EmailerTemplatesExample: {summary: 'Example response for multiple email templates', value: {status: success, data: {emailer_templates: [{id: 88888, group_id: 1, user_id: 42, CreatedAt: '2023-10-18T13:51:39.156291Z', UpdatedAt: '2024-11-19T12:56:27.505651Z', name: 'Newsletter Template', content_html: '<!DOCTYPE html><html><body><h1>Newsletter</h1></body></html>', image_file_id: 888888, image_url: 'https://file-qomon.s3.amazonaws.com/template_88888.jpeg', url: /emailer/template/88888, is_favorite: true, locale: en, category: email}, {id: 88889, group_id: 1, user_id: 42, CreatedAt: '2023-10-19T14:22:15.123456Z', UpdatedAt: '2024-11-20T09:15:33.789012Z', name: 'Event Invitation', content_html: '<!DOCTYPE html><html><body><h1>You''re Invited!</h1></body></html>', image_file_id: 888889, image_url: 'https://file-qomon.s3.amazonaws.com/template_88889.jpeg', url: /emailer/template/88889, is_favorite: false, locale: fr, category: email}]}}}}}}}, '400': {description: 'Bad request due to invalid input.'}, '404': {description: 'User not found or has no templates.'}, '500': {description: 'Internal server error during template retrieval.'}}
    /emailer/template:
        post:
            tags: [Template]
            summary: 'Create a new email template'
            description: "Adds a new email template with specified name, content, and group association.\nGenerates an image preview based on ContentHTML and uploads it.\nOptionally associates the template with a specified folder using `folder_id` within the `emailer_template` object.\nNote: The 'workflow_message' category is reserved for internal workflow message templates \nand should not be used when creating templates via this endpoint.\n"
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [data], properties: {data: {type: object, required: [emailer_template], properties: {emailer_template: {type: object, required: [name, content_html, group_id], properties: {name: {type: string, description: 'Name of the template', example: 'My New Template'}, content_html: {type: string, description: 'HTML content of the template', example: '<!DOCTYPE html><html><body><h1>Hello World</h1></body></html>'}, content_json: {type: string, description: 'JSON content of the template (optional)'}, group_id: {type: integer, description: 'ID of the group the template belongs to', example: 1}, folder_id: {type: integer, description: 'Optional folder ID to organize templates', nullable: true, example: 5}, locale: {type: string, description: 'Locale of the template', example: en, default: en}, category: {$ref: '#/components/schemas/TemplateCategory'}}}}}}}, examples: {CreateTemplateExample: {summary: 'Example request to create a new template', value: {data: {emailer_template: {name: 'My New Template', content_html: '<!DOCTYPE html><html><body><h1>Hello World</h1><p>This is my new template</p></body></html>', content_json: '{"page":{"body":{"container":{"style":{"background-color":"#ffffff"}}}}}', group_id: 1, folder_id: 5, locale: en, category: email}}}}}}}}
            responses: {'200': {description: 'Successfully created the template. Returns the newly created template object.', content: {application/json: {schema: {type: object, required: [status, data], properties: {status: {type: string, enum: [success]}, data: {type: object, required: [emailer_template], properties: {emailer_template: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the template', nullable: true}, name: {type: string, description: 'Name of the template'}, content: {type: string, description: 'HTML content of the template'}, created_at: {type: string, format: date-time, description: 'When the template was created'}, updated_at: {type: string, format: date-time, description: 'When the template was last updated'}, category: {$ref: '#/components/schemas/TemplateCategory'}}}}}}}, examples: {SingleTemplateExample: {summary: 'Example response for a single created template', value: {status: success, data: {emailer_template: {id: 88889, group_id: 1, user_id: 42, CreatedAt: '2025-04-09T15:10:00.000Z', UpdatedAt: '2025-04-09T15:10:00.000Z', name: 'My New Template', content_html: '<!DOCTYPE html><html><body><h1>Hello World</h1></body></html>', image_file_id: 888889, image_url: 'https://file-qomon.s3.amazonaws.com/new_image.jpeg', url: /emailer/template/88889, is_favorite: false, locale: en, category: email}}}}}}}}, '400': {description: 'Bad request due to invalid input (e.g., missing required fields like `name`, `content_html`, `group_id`; malformed JSON body; invalid `folder_id` if provided).', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}}}}, '403': {description: 'Forbidden, user does not have permission to create a template in the specified group.', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}}}}, '404': {description: 'Not Found, the specified `folder_id` does not exist or is not accessible within the group.', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}}}}, '500': {description: 'Internal server error during template creation (e.g., failure during image generation/upload, database error, RPC communication failure).', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}}}}}
        patch:
            tags: [Template]
            summary: 'Update an email template'
            description: 'Updates an existing email template. The template ID must be provided in the request body. If content (HTML/JSON) is updated, a new version is created, otherwise it''s a simple metadata update.'
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [data], properties: {data: {type: object, required: [emailer_template], properties: {emailer_template: {type: object, required: [id], properties: {id: {type: integer, description: 'ID of the template to update', example: 88888}, name: {type: string, description: 'Name of the template', example: 'Updated Template Name'}, content_html: {type: string, description: 'HTML content of the template'}, content_json: {type: string, description: 'JSON content of the template'}, folder_id: {type: integer, description: 'Folder ID to organize templates', nullable: true}, locale: {type: string, description: 'Locale of the template'}, is_favorite: {type: boolean, description: 'Whether the template is marked as favorite'}, category: {$ref: '#/components/schemas/TemplateCategory'}}}}}}}, examples: {UpdateTemplateExample: {summary: 'Example request to update an existing template', value: {data: {emailer_template: {id: 88888, name: 'Updated Template Name', content_html: '<!DOCTYPE html><html><body><h1>Updated Content</h1></body></html>', content_json: '{"page":{"body":{"container":{"style":{"background-color":"#f0f0f0"}}}}}', folder_id: 7, is_favorite: true, category: email}}}}}}}}
            responses: {'200': {description: 'Template updated successfully.', content: {application/json: {schema: {type: object, required: [status, data], properties: {status: {type: string, enum: [success]}, data: {type: object, required: [emailer_template], properties: {emailer_template: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the template', nullable: true}, name: {type: string, description: 'Name of the template'}, content: {type: string, description: 'HTML content of the template'}, created_at: {type: string, format: date-time, description: 'When the template was created'}, updated_at: {type: string, format: date-time, description: 'When the template was last updated'}, category: {$ref: '#/components/schemas/TemplateCategory'}}}}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    '/emailer/template/{id}/history':
        get:
            tags: [Template, History]
            summary: 'Retrieve history for an email template'
            description: "Fetches the version history for a specific email template.\nThe response includes a list representing previous versions (potentially excluding large content fields) and the current version.\n"
            parameters: [{name: id, in: path, required: true, description: 'Unique identifier of the email template to get history for.', schema: {type: integer, format: uint32, minimum: 1}}]
            responses: {'200': {description: 'Successfully retrieved the template history.', content: {application/json: {schema: {type: object, properties: {status: {type: string, enum: [success]}, data: {type: object, properties: {histories: {type: array, description: 'List of template versions (current and previous). Check ''updated_at'' for chronology.', items: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the template', nullable: true}, name: {type: string, description: 'Name of the template'}, content: {type: string, description: 'HTML content of the template'}, created_at: {type: string, format: date-time, description: 'When the template was created'}, updated_at: {type: string, format: date-time, description: 'When the template was last updated'}, category: {$ref: '#/components/schemas/TemplateCategory'}}}}}}}}}}}, '400': {description: 'Bad request due to invalid template ID format in URL path.', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: 'Template not found (the specified ID does not exist or is not accessible within the group).', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}}}}, '500': {description: 'Internal server error during history retrieval (e.g., database error, RPC failure).', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}}}}}
    '/emailer/template/binding/{reason}':
        get:
            tags: [Template]
            summary: 'Retrieve a template binding by reason'
            description: "Returns the template bound to the given reason for the caller's group.\nLookup order:\n1. Binding where the caller's group_id is present in group_ids.\n2. Fallback binding where group_ids is null/empty for the same reason.\n"
            parameters: [{name: reason, in: path, required: true, description: 'Binding reason. Currently only `online_action` and `petition` are supported.', schema: {type: string, enum: [online_action, petition]}}]
            responses: {'200': {description: 'Successfully retrieved the binding template.', content: {application/json: {schema: {type: object, required: [status, data], properties: {status: {type: string, enum: [success]}, data: {type: object, properties: {public_templates: {type: array, description: 'Public template views for the binding (fallback may return a single template)', items: {$ref: '#/components/schemas/PublicTemplateView'}}}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
        post:
            tags: [Template]
            summary: 'Create or update a template binding for a reason'
            description: "Binds a template to a reason for the caller's group.\nOnly `online_action` and `petition` are supported for now.\nThe template must belong to the caller's group.\n"
            parameters: [{name: reason, in: path, required: true, description: 'Binding reason. Currently only `online_action` and `petition` are supported.', schema: {type: string, enum: [online_action, petition]}}]
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [data], properties: {data: {type: object, required: [emailer_template_id], properties: {emailer_template_id: {type: integer, description: 'ID of the template to bind to the reason'}}}}}}}}
            responses: {'200': {description: 'Binding created or updated successfully.', content: {application/json: {schema: {type: object, required: [status, data], properties: {status: {type: string, enum: [success]}, data: {type: object, properties: {public_template: {$ref: '#/components/schemas/PublicTemplateView'}}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    '/emailer/template/binding/reset/{reason}':
        post:
            tags: [Template]
            summary: 'Reset a template binding by reason'
            description: 'Resets a template binding by reason for the caller''s group.'
            parameters: [{name: reason, in: path, required: true, description: 'Binding reason. Currently only `online_action` and `petition` are supported.', schema: {type: string, enum: [online_action, petition]}}]
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [data], properties: {data: {type: object, required: [emailer_template_id], properties: {emailer_template_id: {type: integer, description: 'ID of the template to bind to the reason'}}}}}}}}
            responses: {'200': {description: 'Binding reset successfully.', content: {application/json: {schema: {type: object, required: [status, data], properties: {status: {type: string, enum: [success]}, data: {type: object, properties: {public_template: {$ref: '#/components/schemas/PublicTemplateView'}}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/template/tmp_thumbnail:
        post:
            tags: [Template]
            summary: 'Generate a temporary thumbnail from an email template'
            description: 'Generate a base64-encoded temporary thumbnail from the provided email template HTML content.'
            requestBody: {required: true, content: {application/json: {schema: {type: object, properties: {data: {type: object, properties: {emailer_template: {type: object, properties: {group_id: {type: integer, description: 'The group ID associated with the template.'}, content_html: {type: string, description: 'The HTML content of the email template.'}}}}}}, required: [data]}, examples: {TmpThumbnailRequestExample: {summary: 'Example request for temporary thumbnail generation', value: {data: {emailer_template: {group_id: 1, content_html: '<!DOCTYPE html><html><body><h1>Test Template</h1><p>Content for thumbnail generation</p></body></html>'}}}}}}}}
            responses: {'200': {description: 'Temporary thumbnail generated successfully.', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, properties: {base64_thumbnail: {type: string, description: 'Base64-encoded thumbnail image'}}}}}, examples: {tmpThumbnailExample: {summary: 'Example response for temporary thumbnail', value: {status: success, data: {base64_thumbnail: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/...'}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameters.'}, '500': {description: 'Internal server error encountered during thumbnail generation.'}}
    /emailer/template/realize:
        post:
            tags: [Template]
            summary: 'Realize an email template'
            description: 'Renders an email template with the provided data and returns the rendered content.'
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [data], properties: {data: {type: object, required: [emailer_template_id], properties: {emailer_template_id: {type: integer, description: 'ID of the template to realize', example: 42}, map_models: {type: object, description: 'Map of models for template variables', additionalProperties: true, example: {contact: {firstname: John, lastname: Doe, email: john.doe@example.com}}}}}}}}}}
            responses: {'200': {description: 'Template successfully realized', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, required: [data], properties: {data: {type: object, properties: {content: {type: string, description: 'The rendered template content'}}}}}]}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/template/variables:
        get:
            tags: [Template]
            summary: 'List usable merge-tags'
            description: "Returns the catalogue of merge-tags usable in email templates (grouped, with the tag,\nits `map_models` path, a locale-resolved label, and a coarse `type` hint for how to\nrender/format it). This is the source of truth for the variables the frontend lets\nusers insert.\n\nThis endpoint returns no example/sample values - it is not the backend's job to invent\nlocalized example data. Every variable's `value` is always empty here. To see each\nvariable's actual, correctly localized value for real group data, POST the same\n`map_models` payload used for `/emailer/template/realize` to this same path instead;\nboth endpoints share the exact same reply shape.\n\nPass `template_type` to get only the merge-tags actually usable for that context, e.g.\n`?template_type=campaign` for Brevo marketing campaigns, or `event`/`petition`/`online-form` for the\nmatching transactional template kind. Omit it to get the full, unfiltered catalogue."
            parameters: [{name: locale, in: query, required: false, schema: {type: string}, description: "Locale to resolve labels in (e.g. \"en\", \"fr\"). Falls back to the group's own locale,\nthen to \"en\", when omitted."}, {name: template_type, in: query, required: false, schema: {type: string, enum: [campaign, event, petition, online-form]}, description: "Restricts the returned groups/variables to those usable for this template type. A group or\nvariable with no `template_types` restriction of its own is returned regardless of this filter\n(it applies everywhere). Omit to get the full, unfiltered catalogue."}]
            responses: {'200': {description: 'Merge-tag catalogue (no values)', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, properties: {data: {type: object, description: "The merge-tag catalogue, shared by GET and POST /emailer/template/variables. GET never sets\n`value` on any variable (no backend-authored examples); POST resolves `value` against the\n`map_models` supplied in its request body.", properties: {groups: {type: array, description: 'Variables grouped by source model/category.', items: {$ref: '#/components/schemas/TemplateVariableGroup'}}}}}}]}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
        post:
            tags: [Template]
            summary: 'Resolve merge-tags against real data'
            description: "Resolves the same merge-tag catalogue as `GET /emailer/template/variables`, but against\nreal `map_models` data supplied by the caller (e.g. contact/action/site data taken from\nthe group) - the same shape POSTed to `/emailer/template/realize`. The response shares\nthe exact same shape as the GET catalogue, with each variable's `value` filled in to the\nactual, correctly localized value it resolves to for the given data and locale, instead\nof a backend-authored example.\n\nVariables whose backing data wasn't present in `map_models` stay in the list with an\nempty `value`, so the frontend can still show every usable merge-tag."
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [data], properties: {data: {type: object, properties: {locale: {type: string, description: "Locale to resolve labels and values in (e.g. \"en\", \"fr\"). Falls back to the group's own\nlocale, then to \"en\", when omitted.", example: fr}, template_type: {type: string, enum: [campaign, event, petition, online-form], description: "Restricts the returned groups/variables to those usable for this template type. A group or\nvariable with no `template_types` restriction of its own is returned regardless of this\nfilter (it applies everywhere). Omit to get the full, unfiltered catalogue."}, map_models: {type: object, description: "The same `map_models` shape POSTed to /emailer/template/realize (e.g. real contact/action/site\ndata taken from the group) - each catalogued variable is resolved against it. Variables whose\nbacking data isn't present here resolve to an empty value but stay in the list.", additionalProperties: true, example: {contact: {firstname: Jane, surname: Doe}}}}}}}}}}
            responses: {'200': {description: 'Merge-tag catalogue resolved against the supplied data', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, properties: {data: {type: object, description: "The merge-tag catalogue, shared by GET and POST /emailer/template/variables. GET never sets\n`value` on any variable (no backend-authored examples); POST resolves `value` against the\n`map_models` supplied in its request body.", properties: {groups: {type: array, description: 'Variables grouped by source model/category.', items: {$ref: '#/components/schemas/TemplateVariableGroup'}}}}}}]}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '401': {description: invalid_credit_amount, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/template/duplicate:
        post:
            tags: [Template]
            summary: 'Duplicate an email template'
            description: "Creates an exact copy of a template within the same group.\nThe template must belong to the caller's group.\nThe copy will have the same content and metadata; name gets a suffix like \"(2)\", \"(3)\", etc.\n"
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [data], properties: {data: {type: object, required: [emailer_template], properties: {emailer_template: {type: object, required: [id], properties: {id: {type: integer, description: 'ID of the template to duplicate'}}}}}}}}}}
            responses: {'200': {description: 'Template duplicated successfully', content: {application/json: {schema: {type: object, required: [status, data], properties: {status: {type: string, enum: [success]}, data: {type: object, required: [emailer_template], properties: {emailer_template: {$ref: '#/components/schemas/EmailerTemplate'}}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '403': {description: forbidden, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /sendemail:
        post:
            tags: [Email]
            summary: 'Send an email'
            description: 'Sends an email based on the provided email content and recipient search criteria. Supports both test emails (with direct contact list) and campaign emails (with search criteria).'
            requestBody: {required: true, content: {application/json: {schema: {type: object, properties: {email: {type: object, properties: {object: {type: string, format: html, description: 'HTML content of the email.', example: '<!DOCTYPE html><html><body>...</body></html>'}, subject: {type: string, description: 'Subject line of the email.', example: 'Test sendemail'}, sender: {type: string, format: email, description: 'Email address of the sender.', example: nicolas@qomon.com}, sender_name: {type: string, description: 'Display name of the sender.', example: 'My Company'}, reply_to: {type: string, format: email, description: 'Reply-to email address.', example: support@qomon.com}, cc: {type: string, format: email, description: 'CC email addresses (comma-separated).', example: admin@qomon.com}, scheduled_at: {type: string, format: date-time, description: 'When to schedule the email (optional).', example: '2024-01-15T10:00:00Z'}, contacts: {type: array, items: {type: object, properties: {id: {type: integer, description: 'Contact ID'}, email: {type: string, format: email, description: 'Contact email address'}}}, description: 'Direct contact list for test emails (optional).'}}}, search: {type: object, description: 'Search criteria to select contacts (optional if contacts provided).', properties: {Search: {$ref: '#/components/schemas/Search2'}}}, emailer_template: {type: object, properties: {id: {type: integer, description: 'Template ID to link emailer template.', example: 42}, locale: {type: string, description: 'Template locale.', example: en, default: en}}}}}, examples: {SendEmailTestExample: {summary: 'Example of a test email request (direct contact list)', value: {email: {object: '<!DOCTYPE html><html><body><h1>Test Email</h1><p>This is a test email.</p></body></html>', subject: 'Test Email', sender: test@qomon.com, sender_name: 'Test Sender', reply_to: support@qomon.com, contacts: [{email: recipient1@example.com}, {email: recipient2@example.com}]}}}, SendEmailsWithSearchExample: {summary: 'Example of transactional emails with search request', value: {email: {object: '<!DOCTYPE html><html><body><h1>Newsletter</h1><p>Welcome to our newsletter!</p></body></html>', subject: 'Monthly Newsletter', sender: newsletter@qomon.com, sender_name: 'Newsletter Team', reply_to: unsubscribe@qomon.com, scheduled_at: '2024-01-15T10:00:00Z'}, search: {Search: {fields: ['1', fullname, '1', '0', "", "", "", surname, 'true', "", "", ""], polygon: [], query: 'newsletter subscribers', tags: [newsletter], address_included: [], polling_station_included: [], polling_station_included_missing: false, contact_ids: [4118849, 4118850], advanced_search: {query: {$all: [{$all: []}, {$all: [{$at_least_one: [{$condition: {attr: firstname, ope: eql, value: John}}, {$condition: {attr: surname, ope: eql, value: Doe}}]}]}]}, sort_attr: surname, sort_asc: true, page: 0, per_page: 100}}}, emailer_template: {id: 42, locale: en}}}}}}}
            responses: {'200': {description: 'Email sending started successfully (returns progress information).', content: {application/json: {schema: {type: object, properties: {status: {type: string, description: 'Status of the response.', example: success}, data: {type: object, properties: {in_progress: {$ref: '#/components/schemas/InProgress'}}}}}, examples: {SendEmailTestResponseExample: {summary: 'Example response for email sending (returns progress information)', value: {status: success, data: {in_progress: {count: 150, through_put_seconds: 2.5}}}}}}}}, '400': {description: 'Invalid input in request body.', content: {application/json: {schema: {type: object, properties: {status: {type: string, description: 'Error status.', example: error}, message: {type: string, description: 'Error message.', example: 'Invalid input parameters'}, code: {type: string, description: 'Error code.', example: validation_error}}}}}}, '500': {description: 'Internal server error during email processing.', content: {application/json: {schema: {type: object, properties: {status: {type: string, description: 'Error status.', example: error}, message: {type: string, description: 'Error message.', example: 'Invalid input parameters'}, code: {type: string, description: 'Error code.', example: validation_error}}}}}}}
    /emailcontacts:
        post:
            tags: [Email]
            summary: 'Kpi on contacts that are valids for emailing'
            requestBody: {required: true, content: {application/json: {schema: {type: object, properties: {data: {anyOf: [{$ref: '#/components/schemas/ClassicSearch'}, {properties: {advanced_search: {$ref: '#/components/schemas/AdvancedSearch'}, include_interactions: {type: boolean, example: true}}}]}}}, examples: {EmailContactsExampleRequest: {summary: 'Example request for email contacts KPI', value: {data: {search: {fields: ['1', all, '27', '0', "", "", "", surname, 'true', "", "", ""], polygon: [], query: "", tags: [], address_included: [], polling_station_included: [], polling_station_included_missing: false, advanced_search: {query: {$all: [{$all: [{$condition: {attr: mail, ope: start_with, value: hugo+}}, {$condition: {attr: "", ope: "", value: ""}}]}]}, sort_attr: surname, sort_asc: true, page: 0, per_page: 27}, contact_ids: [4118849]}}}}}}}}
            responses: {'200': {description: 'Email contacts retrieved successfully.', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, properties: {data: {type: object, properties: {original: {type: integer}, valids: {type: integer}, blacklisted: {type: integer}, no_consent: {type: integer}, invalid_mx: {type: integer}, duplicate: {type: integer}, no_email: {type: integer}, group_optin: {type: integer}, email_blacklisted: {type: integer}}}}}]}, examples: {emailContactsExample: {summary: 'Example response for successful email contacts retrieval', value: {status: success, data: {original: 6, valids: 1, no_consent: 2, duplicate: 3, no_email: 1, email_blacklisted: 2}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /getbalanceemail:
        get:
            tags: [Email]
            summary: 'Check email account balance and verify sufficient credits'
            description: "Checks if the emailer account has sufficient credits for sending emails. This endpoint \nverifies the account balance and returns the current balance. If the account does not \nhave enough credits for the requested amount, it returns a 402 Payment Required error.\n"
            responses: {'200': {description: 'Email balance retrieved successfully and account has sufficient credits.', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, properties: {data: {type: object, properties: {balance: {type: number, description: 'The email balance available for the provider', example: 300}}}}}]}, examples: {balanceSuccess: {summary: 'Account has sufficient credits', value: {status: success, data: {balance: 300}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: 'Account not found', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {accountNotFound: {summary: 'Account not found for the group', value: {status: error, message: emailer_account_not_found}}}}}}, '412': {description: 'Precondition failed - insufficient credits', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {notEnoughCredits: {summary: 'Account does not have enough credits', value: {status: error, message: not_enough_credits}}}}}}, '500': {description: 'Internal server error', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {failedToRetrieveAccount: {summary: 'Failed to retrieve account', value: {status: error, message: failed_to_retrieve_account}}, failedToGetEmailer: {summary: 'Failed to get emailer provider', value: {status: error, message: failed_to_get_emailer}}}}}}, '502': {description: 'Bad gateway - provider API error', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {providerGetCreditsFailed: {summary: 'Failed to retrieve credits from provider', value: {status: error, message: provider_get_credits_failed}}}}}}}
    /emails/invalid:
        post:
            tags: [Email]
            summary: 'Get invalid emails from search'
            description: 'Analyzes contacts from a search and returns invalid emails with reasons, plus uploads an Excel file to S3 with detailed information.'
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [search], properties: {search: {type: object, required: [search], properties: {search: {anyOf: [{$ref: '#/components/schemas/ClassicSearch'}, {properties: {advanced_search: {$ref: '#/components/schemas/AdvancedSearch'}, include_interactions: {type: boolean, example: true}}}]}}}}}, examples: {GetInvalidEmailsExample: {summary: 'Get invalid emails example', value: {search: {search: {query: ...}}}}}}}}
            responses: {'200': {description: 'Invalid emails analysis completed successfully.', content: {application/json: {schema: {allOf: [{type: object, required: [status], properties: {status: {type: string, enum: [success]}}}, {type: object, properties: {data: {type: object, properties: {s3_invalid_csv: {type: string, nullable: true, description: 'URL to the Excel file containing invalid emails with reasons (uploaded to S3)', example: 'https://materials.qomon.org/707984b4c219c/123/invalid_emails_123_456_1640995200.xlsx'}}}}}]}, examples: {GetInvalidEmailsResponseExample: {summary: 'Invalid emails response example', value: {status: success, data: {s3_invalid_csv: 'https://materials.qomon.org/707984b4c219c/123/invalid_emails_123_456_1640995200.xlsx'}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/senders:
        get:
            tags: [Senders]
            summary: 'Retrieve all senders associated with the account'
            description: 'Retrieve the list of email senders configured for the account.'
            responses: {'200': {description: 'Successfully retrieved senders.', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, required: [senders], properties: {senders: {type: array, items: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the sender'}, account_id: {type: integer, description: 'Unique identifier for the associated account'}, domain_id: {type: integer, nullable: true, description: 'Unique identifier for the associated domain, if any'}, remote_id: {type: integer, description: 'Unique remote identifier for the sender'}, email: {type: string, description: 'Email address of the sender'}, name: {type: string, description: 'Display name of the sender'}, validated: {type: boolean, description: 'Indicates whether the sender''s email has been validated'}, removable: {type: boolean, description: 'Indicates whether the sender can be removed'}, default_flag: {type: boolean, description: 'Indicates whether this sender is the default sender'}}, required: [email, validated]}}}}}}, examples: {emailerSendersExample: {summary: 'Example response for emailer senders list', value: {status: success, data: {senders: [{id: 123, account_id: 1, domain_id: 45, remote_id: 678, email: sender1@qomon.com, name: 'Marketing Team', validated: true, removable: true, default_flag: true}, {id: 124, account_id: 1, domain_id: 45, remote_id: 679, email: support@qomon.com, name: 'Support Team', validated: true, removable: false, default_flag: false}]}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: 'Account not found', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {accountNotFound: {summary: 'Account not found for the group', value: {status: error, message: emailer_account_not_found}}}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '502': {description: 'Bad gateway - provider API error', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {providerGetSendersFailed: {summary: 'Failed to retrieve senders from provider', value: {status: error, message: provider_get_senders_failed}}}}}}}
        patch:
            tags: [Senders]
            summary: 'Batch update senders'
            description: 'Update multiple email senders at once.'
            requestBody: {required: true, content: {application/json: {schema: {type: object, properties: {emailer_senders: {type: array, items: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the sender', nullable: true}, email: {type: string, description: 'Email address of the sender'}, name: {type: string, description: 'Display name of the sender'}, validated: {type: boolean, description: 'Indicates whether the sender''s email has been validated'}, created_at: {type: string, format: date-time, description: 'When the sender was created'}, updated_at: {type: string, format: date-time, description: 'When the sender was last updated'}}}}}, required: [emailer_senders]}, examples: {batchUpdateSendersExample: {summary: 'Example request to batch update senders', value: {emailer_senders: [{id: 42, name: 'Updated Name'}, {id: 43, name: 'Another Name'}]}}}}}}
            responses: {'200': {description: 'Senders updated successfully.', content: {application/json: {schema: {type: array, items: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the sender', nullable: true}, email: {type: string, description: 'Email address of the sender'}, name: {type: string, description: 'Display name of the sender'}, validated: {type: boolean, description: 'Indicates whether the sender''s email has been validated'}, created_at: {type: string, format: date-time, description: 'When the sender was created'}, updated_at: {type: string, format: date-time, description: 'When the sender was last updated'}}}}, examples: {batchUpdatedSendersExample: {summary: 'Example response for batch updated senders', value: [{id: 42, email: sender@example.com, name: 'Updated Name', validated: true}, {id: 43, email: another@example.com, name: 'Another Name', validated: false}]}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/allsenders:
        get:
            tags: [Senders]
            summary: 'Retrieve all senders from the provider (admin only)'
            description: "Retrieve all email senders from the provider for the account. This endpoint syncs senders \nfrom the provider and returns all senders associated with the account, regardless of user permissions.\nRequires admin privileges.\n"
            responses: {'200': {description: 'Successfully retrieved all senders.', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, required: [senders], properties: {senders: {type: array, items: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the sender'}, account_id: {type: integer, description: 'Unique identifier for the associated account'}, domain_id: {type: integer, nullable: true, description: 'Unique identifier for the associated domain, if any'}, remote_id: {type: integer, description: 'Unique remote identifier for the sender'}, email: {type: string, description: 'Email address of the sender'}, name: {type: string, description: 'Display name of the sender'}, validated: {type: boolean, description: 'Indicates whether the sender''s email has been validated'}, removable: {type: boolean, description: 'Indicates whether the sender can be removed'}, default_flag: {type: boolean, description: 'Indicates whether this sender is the default sender'}}, required: [email, validated]}}}}}}, examples: {emailerSendersExample: {summary: 'Example response for emailer senders list', value: {status: success, data: {senders: [{id: 123, account_id: 1, domain_id: 45, remote_id: 678, email: sender1@qomon.com, name: 'Marketing Team', validated: true, removable: true, default_flag: true}, {id: 124, account_id: 1, domain_id: 45, remote_id: 679, email: support@qomon.com, name: 'Support Team', validated: true, removable: false, default_flag: false}]}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: 'Account not found', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {accountNotFound: {summary: 'Account not found for the group', value: {status: error, message: emailer_account_not_found}}}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '502': {description: 'Bad gateway - provider API error', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}, examples: {providerGetSendersFailed: {summary: 'Failed to retrieve senders from provider', value: {status: error, message: provider_get_senders_failed}}}}}}}
    /emailer/sender:
        post:
            tags: [Senders]
            summary: 'Add a new sender'
            description: 'Add a new email sender to the account.'
            requestBody: {required: true, content: {application/json: {schema: {type: object, properties: {emailer_sender: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the sender', nullable: true}, email: {type: string, description: 'Email address of the sender'}, name: {type: string, description: 'Display name of the sender'}, validated: {type: boolean, description: 'Indicates whether the sender''s email has been validated'}, created_at: {type: string, format: date-time, description: 'When the sender was created'}, updated_at: {type: string, format: date-time, description: 'When the sender was last updated'}}}}, required: [emailer_sender]}, examples: {addSenderExample: {summary: 'Example request to add a sender', value: {emailer_sender: {email: sender@example.com, name: 'Sender Name'}}}}}}}
            responses: {'200': {description: 'Sender added successfully.', content: {application/json: {schema: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the sender', nullable: true}, email: {type: string, description: 'Email address of the sender'}, name: {type: string, description: 'Display name of the sender'}, validated: {type: boolean, description: 'Indicates whether the sender''s email has been validated'}, created_at: {type: string, format: date-time, description: 'When the sender was created'}, updated_at: {type: string, format: date-time, description: 'When the sender was last updated'}}}, examples: {addedSenderExample: {summary: 'Example response for added sender', value: {id: 42, email: sender@example.com, name: 'Sender Name', validated: false}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
        patch:
            tags: [Senders]
            summary: 'Update a sender'
            description: 'Update an existing email sender.'
            requestBody: {required: true, content: {application/json: {schema: {type: object, properties: {emailer_sender: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the sender', nullable: true}, email: {type: string, description: 'Email address of the sender'}, name: {type: string, description: 'Display name of the sender'}, validated: {type: boolean, description: 'Indicates whether the sender''s email has been validated'}, created_at: {type: string, format: date-time, description: 'When the sender was created'}, updated_at: {type: string, format: date-time, description: 'When the sender was last updated'}}}}, required: [emailer_sender]}, examples: {updateSenderExample: {summary: 'Example request to update a sender', value: {emailer_sender: {id: 42, name: 'Updated Name'}}}}}}}
            responses: {'200': {description: 'Sender updated successfully.', content: {application/json: {schema: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the sender', nullable: true}, email: {type: string, description: 'Email address of the sender'}, name: {type: string, description: 'Display name of the sender'}, validated: {type: boolean, description: 'Indicates whether the sender''s email has been validated'}, created_at: {type: string, format: date-time, description: 'When the sender was created'}, updated_at: {type: string, format: date-time, description: 'When the sender was last updated'}}}, examples: {updatedSenderExample: {summary: 'Example response for updated sender', value: {id: 42, email: sender@example.com, name: 'Updated Name', validated: true}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/sender/validate:
        post:
            tags: [Senders]
            summary: 'Validate a sender via OTP'
            description: 'Validate an email sender using an OTP code (for example, Brevo/Sendinblue sender validation).'
            requestBody: {required: true, content: {application/json: {schema: {type: object, properties: {emailer_sender: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the sender', nullable: true}, email: {type: string, description: 'Email address of the sender'}, name: {type: string, description: 'Display name of the sender'}, validated: {type: boolean, description: 'Indicates whether the sender''s email has been validated'}, created_at: {type: string, format: date-time, description: 'When the sender was created'}, updated_at: {type: string, format: date-time, description: 'When the sender was last updated'}}}, otp_code: {type: integer, description: 'OTP code sent to the sender''s email'}}, required: [emailer_sender, otp_code]}, examples: {validateSenderExample: {summary: 'Example request to validate a sender', value: {emailer_sender: {id: 42}, otp_code: 123456}}}}}}
            responses: {'200': {description: 'Sender validated successfully.', content: {application/json: {schema: {type: object, properties: {id: {type: integer}, email: {type: string}, name: {type: string}, validated: {type: boolean}, remote_id: {type: integer}, default_flag: {type: boolean}}}, examples: {validatedSenderExample: {summary: 'Example response for validated sender', value: {id: 42, email: sender@example.com, name: 'Sender Name', validated: true, remote_id: 123456, default_flag: false}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    '/emailer/sender/{id}':
        delete:
            tags: [Senders]
            summary: 'Delete a sender'
            description: 'Delete an email sender by ID.'
            parameters: [{in: path, name: id, required: true, schema: {type: integer}, description: 'ID of the sender to delete'}]
            responses: {'200': {description: 'Sender deleted successfully.', content: {application/json: {schema: {type: object, properties: {success: {type: boolean}, message: {type: string}}}, examples: {deletedSenderExample: {summary: 'Example response for deleted sender', value: {success: true, message: 'Sender deleted successfully.'}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/domains:
        get:
            tags: [Domains]
            summary: 'Retrieve all domains'
            description: 'Retrieve the list of email domains configured for the account.'
            responses: {'200': {description: 'Successfully retrieved domains.', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, properties: {emailer_domains: {type: array, items: {type: object, properties: {CreatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}}}}}}}}, examples: {domainsListExample: {summary: 'Example response for domains list', value: {status: success, data: {emailer_domains: [{id: 123, name: example.com, custom_verp: bounce.example.com, removable: true, is_valid: true, validation: {spf: true, dkim: true, dmarc: true, tracking: true, verp: true}, dns_record: {dkim1_record_type: TXT, dkim1_record_hostname: mail._domainkey.example.com, dkim1_record_value: 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...', dkim2_record_type: TXT, dkim2_record_hostname: mail2._domainkey.example.com, dkim2_record_value: 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD...', dmarc_record_type: TXT, dmarc_record_hostname: _dmarc.example.com, dmarc_record_value: 'v=DMARC1; p=none; rua=mailto:dmarc@example.com', brevo_code_record_type: TXT, brevo_code_record_hostname: example.com, brevo_code_record_value: brevo-code=abc123def456, spf_record_type: TXT, spf_record_hostname: example.com, spf_record_value: 'v=spf1 include:spf.sendinblue.com ~all'}}, {id: 124, name: test.com, custom_verp: "", removable: true, is_valid: false, validation: {spf: false, dkim: false, dmarc: false, tracking: false, verp: false}, dns_record: {dkim1_record_type: TXT, dkim1_record_hostname: mail._domainkey.test.com, dkim1_record_value: 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQE...', dkim2_record_type: TXT, dkim2_record_hostname: mail2._domainkey.test.com, dkim2_record_value: 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQF...', dmarc_record_type: TXT, dmarc_record_hostname: _dmarc.test.com, dmarc_record_value: 'v=DMARC1; p=none; rua=mailto:dmarc@test.com', brevo_code_record_type: TXT, brevo_code_record_hostname: test.com, brevo_code_record_value: brevo-code=xyz789uvw012, spf_record_type: TXT, spf_record_hostname: test.com, spf_record_value: 'v=spf1 include:spf.sendinblue.com ~all'}}]}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
        post:
            tags: [Domains]
            summary: 'Add a new domain'
            description: 'Add a new email domain to the account for email sending verification.'
            requestBody: {required: true, content: {application/json: {schema: {type: object, properties: {emailer_domain: {type: object, properties: {name: {type: string, description: 'Domain name to add', example: example.com}, custom_verp: {type: string, description: 'Custom VERP (Variable Envelope Return Path) setting', example: bounce.example.com}}}}, required: [emailer_domain]}, examples: {addDomainExample: {summary: 'Example request to add a domain', value: {emailer_domain: {name: example.com, custom_verp: bounce.example.com}}}}}}}
            responses: {'200': {description: 'Domain added successfully.', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, properties: {emailer_domain: {type: object, properties: {CreatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}}}}}}}, examples: {addedDomainExample: {summary: 'Example response for added domain', value: {status: success, data: {emailer_domain: {id: 123, name: example.com, custom_verp: bounce.example.com, removable: true, is_valid: false, validation: {spf: false, dkim: false, dmarc: false, tracking: false, verp: false}, dns_record: {dkim1_record_type: TXT, dkim1_record_hostname: mail._domainkey.example.com, dkim1_record_value: 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...', dkim2_record_type: TXT, dkim2_record_hostname: mail2._domainkey.example.com, dkim2_record_value: 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD...', dmarc_record_type: TXT, dmarc_record_hostname: _dmarc.example.com, dmarc_record_value: 'v=DMARC1; p=none; rua=mailto:dmarc@example.com', brevo_code_record_type: TXT, brevo_code_record_hostname: example.com, brevo_code_record_value: brevo-code=abc123def456, spf_record_type: TXT, spf_record_hostname: example.com, spf_record_value: 'v=spf1 include:spf.sendinblue.com ~all'}}}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    '/emailer/domains/{id}':
        delete:
            tags: [Domains]
            summary: 'Delete a domain'
            description: 'Delete an email domain by ID. This will also remove associated senders.'
            parameters: [{in: path, name: id, required: true, schema: {type: integer}, description: 'ID of the domain to delete'}]
            responses: {'200': {description: 'Domain deleted successfully. Returns the complete account information.', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, properties: {CreatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, UpdatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, DeletedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z', nullable: true}, id: {type: integer}, group_id: {type: integer}, credits: {type: integer}, provider_id: {type: integer}, provider: {$ref: '#/components/schemas/EmailerProvider'}, senders: {$ref: '#/components/schemas/EmailerSenders'}, domains: {type: array, items: {$ref: '#/components/schemas/EmailerDomain'}}, credits_refill: {$ref: '#/components/schemas/EmailerCreditRefill'}, subscription: {$ref: '#/components/schemas/EmailerSubscription'}, default_flag: {type: boolean}, sub_account_id: {type: integer, description: 'ID of the sub-account, can be used to know if account is legacy', nullable: true}}}}}, examples: {deletedDomainExample: {summary: 'Example response for deleted domain', value: {status: success, data: {id: 1, group_id: 123, credits: 5000, provider_id: 1, provider: {id: 1, name: ElasticEmail}, senders: [], domains: [], default_flag: true}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    '/emailer/domains/{id}/verify':
        get:
            tags: [Domains]
            summary: 'Verify domain configuration'
            description: 'Verify DNS records and domain configuration for email sending (SPF, DKIM, DMARC, tracking, VERP).'
            parameters: [{in: path, name: id, required: true, schema: {type: integer}, description: 'ID of the domain to verify'}]
            responses: {'200': {description: 'Domain verification completed. Returns validation status for all DNS records.', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, properties: {emailer_domain: {type: object, properties: {CreatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}}}}}}}, examples: {verifiedDomainExample: {summary: 'Example response for domain verification', value: {status: success, data: {emailer_domain: {id: 123, name: example.com, custom_verp: bounce.example.com, removable: true, is_valid: true, validation: {spf: true, dkim: true, dmarc: true, tracking: true, verp: true}, dns_record: {dkim1_record_type: TXT, dkim1_record_hostname: mail._domainkey.example.com, dkim1_record_value: 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...', dkim2_record_type: TXT, dkim2_record_hostname: mail2._domainkey.example.com, dkim2_record_value: 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD...', dmarc_record_type: TXT, dmarc_record_hostname: _dmarc.example.com, dmarc_record_value: 'v=DMARC1; p=none; rua=mailto:dmarc@example.com', brevo_code_record_type: TXT, brevo_code_record_hostname: example.com, brevo_code_record_value: brevo-code=abc123def456, spf_record_type: TXT, spf_record_hostname: example.com, spf_record_value: 'v=spf1 include:spf.sendinblue.com ~all'}}}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /emailer/domains/verp:
        patch:
            tags: [Domains]
            summary: 'Set VERP configuration'
            description: 'Configure VERP (Variable Envelope Return Path) settings for a domain.'
            requestBody: {required: true, content: {application/json: {schema: {type: object, properties: {emailer_domain: {type: object, properties: {id: {type: integer, description: 'ID of the domain to configure VERP for'}, account_id: {type: integer, description: 'Account ID that owns the domain'}, custom_verp: {type: string, description: 'Custom VERP domain setting', example: bounce.example.com}}}}, required: [emailer_domain]}, examples: {setVerpExample: {summary: 'Example request to set VERP', value: {emailer_domain: {id: 123, account_id: 1, custom_verp: bounce.example.com}}}}}}}
            responses: {'200': {description: 'VERP configuration updated successfully.', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, properties: {emailer_domain: {type: object, properties: {CreatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}}}}}}}, examples: {verpSetExample: {summary: 'Example response for VERP configuration', value: {status: success, data: {emailer_domain: {id: 123, name: example.com, custom_verp: bounce.example.com, removable: true, is_valid: true, validation: {spf: true, dkim: true, dmarc: true, tracking: true, verp: true}, dns_record: {dkim1_record_type: TXT, dkim1_record_hostname: mail._domainkey.example.com, dkim1_record_value: 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...', dkim2_record_type: TXT, dkim2_record_hostname: mail2._domainkey.example.com, dkim2_record_value: 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD...', dmarc_record_type: TXT, dmarc_record_hostname: _dmarc.example.com, dmarc_record_value: 'v=DMARC1; p=none; rua=mailto:dmarc@example.com', brevo_code_record_type: TXT, brevo_code_record_hostname: example.com, brevo_code_record_value: brevo-code=abc123def456, spf_record_type: TXT, spf_record_hostname: example.com, spf_record_value: 'v=spf1 include:spf.sendinblue.com ~all'}}}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: account_not_found, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
    /autosends:
        get:
            tags: [Autosend]
            summary: 'Retrieve the automatic email settings'
            description: "Retrieve the automatic (\"smart\") email settings of the authenticated group.\nThese settings define the subject, header, footer, gender labels and sender\nused by emails sent automatically when a survey answer with an auto-send\ntext is collected. Requires admin privileges.\n"
            responses: {'200': {description: 'Successfully retrieved the automatic email settings.', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, required: [autosend], properties: {autosend: {type: object, description: "Per-group settings for automatic (\"smart\") emails, sent when a survey answer\ncarrying an auto-send text is collected for a contact. The `id` equals the\ngroup id and is set server-side.\n", properties: {id: {type: integer, readOnly: true, description: 'Group id owning these settings. Set server-side from the authenticated group.', example: 2495}, emailacc_id: {type: integer, deprecated: true, description: 'Legacy email account id. Stored but no longer used when sending.'}, subject: {type: string, description: 'Subject of the automatic email. Supports the `$firstname`, `$surname` and `$gender` placeholders.', example: 'Thank you for being there, $firstname!'}, header: {type: string, description: 'HTML fragment prepended before the auto-send texts of the answered questions. Supports the same placeholders as `subject`.'}, footer: {type: string, description: 'HTML fragment appended after the auto-send texts of the answered questions. Supports the same placeholders as `subject`.'}, gender_label_homme: {type: string, description: 'Text replacing the `$gender` placeholder for male contacts.'}, gender_label_femme: {type: string, description: 'Text replacing the `$gender` placeholder for female contacts.'}, gender_label_neutre: {type: string, description: 'Text replacing the `$gender` placeholder when the contact gender is unknown or neutral.'}, cc: {type: string, format: email, description: 'Optional single Cc recipient. Cc''ing costs one extra email credit per send.'}, reply_to: {type: string, format: email, description: 'Optional Reply-To address.'}, sender: {type: string, format: email, description: "Sender address, expected to be one of the account's validated senders.\nWhen empty, the group's default validated sender is used at send time;\nif the group has no validated sender, a generated `no_reply_...` address\nis used as a last resort.\n", example: sender@example.com}}}}}}}, examples: {autosendExample: {summary: 'Example automatic email settings', value: {status: success, data: {autosend: {id: 2495, emailacc_id: 0, subject: 'Thank you for being there, $firstname!', header: '<p>Hello $firstname $surname,</p>', footer: '<p>See you soon!</p>', gender_label_homme: 'Dear Sir', gender_label_femme: 'Dear Madam', gender_label_neutre: Dear, cc: "", reply_to: contact@example.com, sender: sender@example.com}}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '404': {description: 'No automatic email settings configured for the group.', content: {application/json: {schema: {allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
        post:
            tags: [Autosend]
            summary: 'Create the automatic email settings'
            description: "Create the automatic email settings of the authenticated group. A group has\na single settings record; its id is the group id and is set server-side.\nRequires admin privileges.\n"
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [autosend], properties: {autosend: {type: object, description: "Per-group settings for automatic (\"smart\") emails, sent when a survey answer\ncarrying an auto-send text is collected for a contact. The `id` equals the\ngroup id and is set server-side.\n", properties: {id: {type: integer, readOnly: true, description: 'Group id owning these settings. Set server-side from the authenticated group.', example: 2495}, emailacc_id: {type: integer, deprecated: true, description: 'Legacy email account id. Stored but no longer used when sending.'}, subject: {type: string, description: 'Subject of the automatic email. Supports the `$firstname`, `$surname` and `$gender` placeholders.', example: 'Thank you for being there, $firstname!'}, header: {type: string, description: 'HTML fragment prepended before the auto-send texts of the answered questions. Supports the same placeholders as `subject`.'}, footer: {type: string, description: 'HTML fragment appended after the auto-send texts of the answered questions. Supports the same placeholders as `subject`.'}, gender_label_homme: {type: string, description: 'Text replacing the `$gender` placeholder for male contacts.'}, gender_label_femme: {type: string, description: 'Text replacing the `$gender` placeholder for female contacts.'}, gender_label_neutre: {type: string, description: 'Text replacing the `$gender` placeholder when the contact gender is unknown or neutral.'}, cc: {type: string, format: email, description: 'Optional single Cc recipient. Cc''ing costs one extra email credit per send.'}, reply_to: {type: string, format: email, description: 'Optional Reply-To address.'}, sender: {type: string, format: email, description: "Sender address, expected to be one of the account's validated senders.\nWhen empty, the group's default validated sender is used at send time;\nif the group has no validated sender, a generated `no_reply_...` address\nis used as a last resort.\n", example: sender@example.com}}}}}, examples: {createAutosendExample: {summary: 'Example request to create the settings', value: {autosend: {subject: 'Thank you for being there, $firstname!', header: '<p>Hello $firstname $surname,</p>', footer: '<p>See you soon!</p>', gender_label_homme: 'Dear Sir', gender_label_femme: 'Dear Madam', gender_label_neutre: Dear, sender: sender@example.com}}}}}}}
            responses: {'200': {description: 'Automatic email settings created successfully.', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, required: [autosend], properties: {autosend: {type: object, description: "Per-group settings for automatic (\"smart\") emails, sent when a survey answer\ncarrying an auto-send text is collected for a contact. The `id` equals the\ngroup id and is set server-side.\n", properties: {id: {type: integer, readOnly: true, description: 'Group id owning these settings. Set server-side from the authenticated group.', example: 2495}, emailacc_id: {type: integer, deprecated: true, description: 'Legacy email account id. Stored but no longer used when sending.'}, subject: {type: string, description: 'Subject of the automatic email. Supports the `$firstname`, `$surname` and `$gender` placeholders.', example: 'Thank you for being there, $firstname!'}, header: {type: string, description: 'HTML fragment prepended before the auto-send texts of the answered questions. Supports the same placeholders as `subject`.'}, footer: {type: string, description: 'HTML fragment appended after the auto-send texts of the answered questions. Supports the same placeholders as `subject`.'}, gender_label_homme: {type: string, description: 'Text replacing the `$gender` placeholder for male contacts.'}, gender_label_femme: {type: string, description: 'Text replacing the `$gender` placeholder for female contacts.'}, gender_label_neutre: {type: string, description: 'Text replacing the `$gender` placeholder when the contact gender is unknown or neutral.'}, cc: {type: string, format: email, description: 'Optional single Cc recipient. Cc''ing costs one extra email credit per send.'}, reply_to: {type: string, format: email, description: 'Optional Reply-To address.'}, sender: {type: string, format: email, description: "Sender address, expected to be one of the account's validated senders.\nWhen empty, the group's default validated sender is used at send time;\nif the group has no validated sender, a generated `no_reply_...` address\nis used as a last resort.\n", example: sender@example.com}}}}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
        patch:
            tags: [Autosend]
            summary: 'Update the automatic email settings'
            description: "Update the automatic email settings of the authenticated group. The full\nsettings object is saved as provided. Requires admin privileges.\n"
            requestBody: {required: true, content: {application/json: {schema: {type: object, required: [autosend], properties: {autosend: {type: object, description: "Per-group settings for automatic (\"smart\") emails, sent when a survey answer\ncarrying an auto-send text is collected for a contact. The `id` equals the\ngroup id and is set server-side.\n", properties: {id: {type: integer, readOnly: true, description: 'Group id owning these settings. Set server-side from the authenticated group.', example: 2495}, emailacc_id: {type: integer, deprecated: true, description: 'Legacy email account id. Stored but no longer used when sending.'}, subject: {type: string, description: 'Subject of the automatic email. Supports the `$firstname`, `$surname` and `$gender` placeholders.', example: 'Thank you for being there, $firstname!'}, header: {type: string, description: 'HTML fragment prepended before the auto-send texts of the answered questions. Supports the same placeholders as `subject`.'}, footer: {type: string, description: 'HTML fragment appended after the auto-send texts of the answered questions. Supports the same placeholders as `subject`.'}, gender_label_homme: {type: string, description: 'Text replacing the `$gender` placeholder for male contacts.'}, gender_label_femme: {type: string, description: 'Text replacing the `$gender` placeholder for female contacts.'}, gender_label_neutre: {type: string, description: 'Text replacing the `$gender` placeholder when the contact gender is unknown or neutral.'}, cc: {type: string, format: email, description: 'Optional single Cc recipient. Cc''ing costs one extra email credit per send.'}, reply_to: {type: string, format: email, description: 'Optional Reply-To address.'}, sender: {type: string, format: email, description: "Sender address, expected to be one of the account's validated senders.\nWhen empty, the group's default validated sender is used at send time;\nif the group has no validated sender, a generated `no_reply_...` address\nis used as a last resort.\n", example: sender@example.com}}}}}, examples: {updateAutosendExample: {summary: 'Example request to update the sender', value: {autosend: {subject: 'Thank you for being there, $firstname!', header: '<p>Hello $firstname $surname,</p>', footer: '<p>See you soon!</p>', sender: new-sender@example.com}}}}}}}
            responses: {'200': {description: 'Automatic email settings updated successfully.', content: {application/json: {schema: {type: object, properties: {status: {type: string, example: success}, data: {type: object, required: [autosend], properties: {autosend: {type: object, description: "Per-group settings for automatic (\"smart\") emails, sent when a survey answer\ncarrying an auto-send text is collected for a contact. The `id` equals the\ngroup id and is set server-side.\n", properties: {id: {type: integer, readOnly: true, description: 'Group id owning these settings. Set server-side from the authenticated group.', example: 2495}, emailacc_id: {type: integer, deprecated: true, description: 'Legacy email account id. Stored but no longer used when sending.'}, subject: {type: string, description: 'Subject of the automatic email. Supports the `$firstname`, `$surname` and `$gender` placeholders.', example: 'Thank you for being there, $firstname!'}, header: {type: string, description: 'HTML fragment prepended before the auto-send texts of the answered questions. Supports the same placeholders as `subject`.'}, footer: {type: string, description: 'HTML fragment appended after the auto-send texts of the answered questions. Supports the same placeholders as `subject`.'}, gender_label_homme: {type: string, description: 'Text replacing the `$gender` placeholder for male contacts.'}, gender_label_femme: {type: string, description: 'Text replacing the `$gender` placeholder for female contacts.'}, gender_label_neutre: {type: string, description: 'Text replacing the `$gender` placeholder when the contact gender is unknown or neutral.'}, cc: {type: string, format: email, description: 'Optional single Cc recipient. Cc''ing costs one extra email credit per send.'}, reply_to: {type: string, format: email, description: 'Optional Reply-To address.'}, sender: {type: string, format: email, description: "Sender address, expected to be one of the account's validated senders.\nWhen empty, the group's default validated sender is used at send time;\nif the group has no validated sender, a generated `no_reply_...` address\nis used as a last resort.\n", example: sender@example.com}}}}}}}}}}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
        delete:
            tags: [Autosend]
            summary: 'Delete the automatic email settings'
            description: "Delete the automatic email settings of the authenticated group. Automatic\nemails will no longer have a subject, header, footer or configured sender.\nRequires admin privileges.\n"
            responses: {'204': {description: 'Automatic email settings deleted successfully.'}, '400': {description: 'Invalid request, such as lacking required request body or parameter', content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}, '500': {description: internal_error, content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}}}
components:
    responses:
        BadRequest:
            description: 'Invalid request, such as lacking required request body or parameter'
            content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
        Forbidden:
            description: forbidden
            content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
        NotFound:
            description: account_not_found
            content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
        Unauthorized:
            description: invalid_credit_amount
            content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
        UnexpectedError:
            description: internal_error
            content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
    schemas:
        PublicTemplateView:
            type: object
            description: 'Public template view returned by binding endpoints'
            properties: {public_id: {type: integer, description: 'Public identifier (matches template id; may be nullified in responses)', nullable: true}, name: {type: string}, image_url: {type: string, nullable: true}, content_html: {type: string, nullable: true}, content_json: {type: string, nullable: true}, created_at: {type: string, format: date-time}, updated_at: {type: string, format: date-time}}
        EmailerTemplateBindingRequest:
            type: object
            required: [data]
            properties: {data: {type: object, required: [emailer_template_id], properties: {emailer_template_id: {type: integer, description: 'ID of the template to bind to the reason'}}}}
        EmailerTemplateBindingGetResponse:
            type: object
            required: [status, data]
            properties: {status: {type: string, enum: [success]}, data: {type: object, properties: {public_templates: {type: array, description: 'Public template views for the binding (fallback may return a single template)', items: {$ref: '#/components/schemas/PublicTemplateView'}}}}}
        EmailerTemplateBindingResponse:
            type: object
            required: [status, data]
            properties: {status: {type: string, enum: [success]}, data: {type: object, properties: {public_template: {$ref: '#/components/schemas/PublicTemplateView'}}}}
        Error:
            allOf: [{$ref: '#/components/schemas/ResponseError'}, {type: object, required: [message], properties: {message: {description: 'A human readable error message usually considered presentable on user interfaces', type: string}}}]
        ResponseError:
            type: object
            required: [status]
            properties: {status: {type: string, enum: [error, fail]}}
        ResponseSuccess:
            type: object
            required: [status]
            properties: {status: {type: string, enum: [success]}}
        Autosend:
            type: object
            description: "Per-group settings for automatic (\"smart\") emails, sent when a survey answer\ncarrying an auto-send text is collected for a contact. The `id` equals the\ngroup id and is set server-side.\n"
            properties: {id: {type: integer, readOnly: true, description: 'Group id owning these settings. Set server-side from the authenticated group.', example: 2495}, emailacc_id: {type: integer, deprecated: true, description: 'Legacy email account id. Stored but no longer used when sending.'}, subject: {type: string, description: 'Subject of the automatic email. Supports the `$firstname`, `$surname` and `$gender` placeholders.', example: 'Thank you for being there, $firstname!'}, header: {type: string, description: 'HTML fragment prepended before the auto-send texts of the answered questions. Supports the same placeholders as `subject`.'}, footer: {type: string, description: 'HTML fragment appended after the auto-send texts of the answered questions. Supports the same placeholders as `subject`.'}, gender_label_homme: {type: string, description: 'Text replacing the `$gender` placeholder for male contacts.'}, gender_label_femme: {type: string, description: 'Text replacing the `$gender` placeholder for female contacts.'}, gender_label_neutre: {type: string, description: 'Text replacing the `$gender` placeholder when the contact gender is unknown or neutral.'}, cc: {type: string, format: email, description: 'Optional single Cc recipient. Cc''ing costs one extra email credit per send.'}, reply_to: {type: string, format: email, description: 'Optional Reply-To address.'}, sender: {type: string, format: email, description: "Sender address, expected to be one of the account's validated senders.\nWhen empty, the group's default validated sender is used at send time;\nif the group has no validated sender, a generated `no_reply_...` address\nis used as a last resort.\n", example: sender@example.com}}
        EmailerCampaignView:
            type: object
            properties: {id: {type: integer, description: 'Unique identifier for the campaign'}, goal: {type: string, description: 'Campaign goal'}, campaign_name: {type: string, description: 'Name of the campaign'}, sender: {$ref: '#/components/schemas/EmailerSender'}, template: {$ref: '#/components/schemas/EmailerTemplate'}, saved_filter: {$ref: '#/components/schemas/SavedFilter'}, user_id: {type: integer, description: 'ID of the user who created the campaign'}, reply_to: {type: string, description: 'Reply-to email address'}, tested: {type: boolean, nullable: true, description: 'Whether the campaign has been tested'}, group_id: {type: integer, description: 'ID of the group the campaign belongs to'}, created_at: {type: string, format: date-time, description: 'When the campaign was created'}, updated_at: {type: string, format: date-time, description: 'When the campaign was last updated'}, provider: {type: string, description: 'Email provider used for the campaign'}, subject: {type: string, description: 'Subject line of the campaign'}, status: {type: string, enum: [draft, pending, scheduled, running, rejected, cancelled, started, archived, failed], description: 'Current status of the campaign'}, contacts_selected: {type: integer, description: 'Number of contacts selected for the campaign'}, scheduled_at: {type: string, format: date-time, nullable: true, description: 'When the campaign is scheduled to be sent'}, started_at: {type: string, format: date-time, nullable: true, description: 'When the campaign was started'}, state: {$ref: '#/components/schemas/EmailerCampaignState'}, steps: {type: array, items: {$ref: '#/components/schemas/EmailerCampaignStep'}}, stats: {$ref: '#/components/schemas/EmailerCampaignStats'}, is_successful: {type: boolean, nullable: true, description: 'Whether the campaign was successful (for performance tracking)'}, delivered_count: {type: integer, nullable: true, description: 'Number of emails delivered (Only present for archived campaigns)'}, open_count: {type: integer, nullable: true, description: 'Number of emails opened (Only present for archived campaigns)'}, unique_click_count: {type: integer, nullable: true, description: 'Number of unique clicks (Only present for archived campaigns)'}, hard_bounce_count: {type: integer, nullable: true, description: 'Number of hard bounces (Only present for archived campaigns)'}, unsubscribe_count: {type: integer, nullable: true, description: 'Number of unsubscribes (Only present for archived campaigns)'}, spam_complaints_count: {type: integer, nullable: true, description: 'Number of spam complaints (Only present for archived campaigns)'}}
        EmailerCampaignViewUnique:
            allOf: [{$ref: '#/components/schemas/EmailerCampaignView'}, {type: object, properties: {links: {type: array, items: {$ref: '#/components/schemas/CampaignStepLink'}}}}]
        EmailerCampaignViews:
            type: object
            properties: {campaigns: {type: array, items: {$ref: '#/components/schemas/EmailerCampaignView'}}, total: {type: integer, description: 'Total number of campaigns in current page'}, page: {type: integer, description: 'Current page number'}, per_page: {type: integer, description: 'Number of campaigns per page'}, all_count: {type: integer, description: 'Total number of campaigns across all pages'}}
        EmailerCampaignState:
            type: object
            properties: {sender_set: {type: boolean, nullable: true, description: 'Whether a sender has been set'}, audience_set: {type: boolean, nullable: true, description: 'Whether an audience has been selected'}, template_set: {type: boolean, nullable: true, description: 'Whether a template has been set'}, subject_set: {type: boolean, nullable: true, description: 'Whether a subject has been set'}, progress: {type: number, minimum: 0, maximum: 1, description: 'Campaign completion progress (0.0 to 1.0)'}}
        EmailerCampaignStep:
            type: object
            properties: {order: {type: integer, description: 'Step order in the process (0-8)'}, name: {type: string, enum: [campaign_verification, format_template_with_attributes, qomon_audiences_brevo_folder, qomon_webhook, create_brevo_list_into_qomon_audiences_folder, import_contacts_into_brevo_list, create_brevo_campaign, linking_emails_to_campaign, start_brevo_campaign], description: "Name of the step. Steps run in order:\n- `campaign_verification` (0): pre-send validation\n- `format_template_with_attributes` (1): sync Brevo attributes + render template\n- `qomon_audiences_brevo_folder` (2): get/create the Brevo folder\n- `qomon_webhook` (3): set up the event webhook (real send only)\n- `create_brevo_list_into_qomon_audiences_folder` (4): create the contact list\n- `import_contacts_into_brevo_list` (5): import contacts + monitor the import\n- `create_brevo_campaign` (6): create the campaign in Brevo\n- `linking_emails_to_campaign` (7): persist the email records\n- `start_brevo_campaign` (8): send (or send the test)"}, status: {type: string, enum: [todo, in_progress, done, error], description: 'Current status of the step'}, error: {type: string, nullable: true, description: "Machine-readable error code when `status` is `error`, used by the frontend to classify\nthe failure. The stored string is wrapped (`__STATUSERROR_V1__:status <http>: <code>`); the\nclassifiable token is `<code>`.\n\n**Codes by step** (see the `POST /emailer/campaign/send` \"Error Handling\" section for the\nfull catalogue with triggers):\n- `campaign_verification`: `campaign_not_in_right_status`, `template_not_set`, `sender_not_set`,\n  `saved_filter_not_set`, `audience_not_set`, `verify_campaign_error`, `subject_missing`,\n  `invalid_reply_to`, `scheduled_at_in_the_past`, `update_campaign_status_error`,\n  `retrieve_contacts_error`, `realize_template_error`, `get_campaign_error`,\n  `mailjet_api_keys_missing`, `campaign_id_required`, `group_id_required`\n- `format_template_with_attributes`: `realize_template_error`, `Failed to retrieve attributes`,\n  `Failed to create GENDER attribute`, `Failed to create MARITAL_NAME attribute`,\n  `Failed to create QOMON_ID attribute`\n- `qomon_audiences_brevo_folder`, `qomon_webhook`,\n  `create_brevo_list_into_qomon_audiences_folder`, `import_contacts_into_brevo_list`,\n  `start_brevo_campaign`: `brevo_api_error_code_<CODE>`, `brevo_api_error`,\n  `api_error_<context>` (and for import: `import process took too long for process: <ID>`)\n- `create_brevo_campaign`: `marshal_campaign_tag_error`, `scheduled_at_in_the_past`,\n  `brevo_api_error_code_<CODE>`, `brevo_api_error`\n- `linking_emails_to_campaign`: `SaveEmails: no emails to save`\n- persistence infra (any step): `update_campaign_step_error`, `update_campaign_error`,\n  `get_attributes_error`"}}
        EmailerCampaignStats:
            type: object
            properties: {open_rate: {type: number, nullable: true, minimum: 0, maximum: 100, description: 'Email open rate as percentage (0-100)'}, click_rate: {type: number, nullable: true, minimum: 0, maximum: 100, description: 'Email click rate as percentage (0-100)'}, unsub_rate: {type: number, nullable: true, minimum: 0, maximum: 100, description: 'Unsubscribe rate as percentage (0-100)'}, rating: {type: number, nullable: true, minimum: 0, maximum: 100, description: 'Performance rating as percentage (0-100)'}}
        CampaignStepLink:
            type: object
            properties: {rel: {type: string, description: 'Link relationship'}, href: {type: string, description: 'Link URL'}}
        CampaignReport:
            type: object
            properties: {unique_clicks: {type: integer, description: 'Number of unique clicks'}, clickers: {type: integer, description: 'Number of unique clickers'}, complaints: {type: integer, description: 'Number of complaints/abuse reports'}, delivered: {type: integer, description: 'Number of emails delivered'}, sent: {type: integer, description: 'Number of emails sent'}, soft_bounces: {type: integer, description: 'Number of soft bounces'}, hard_bounces: {type: integer, description: 'Number of hard bounces'}, unique_views: {type: integer, description: 'Number of unique views'}, trackable_views: {type: integer, description: 'Number of trackable views'}, trackable_views_rate: {type: number, description: 'Trackable views rate as decimal'}, estimated_views: {type: integer, description: 'Estimated number of views'}, unsubscriptions: {type: integer, description: 'Number of unsubscriptions'}, viewed: {type: integer, description: 'Number of emails viewed'}, deferred: {type: integer, description: 'Number of emails deferred'}, return_bounce: {type: integer, description: 'Number of return bounces'}}
        InvalidContactRow:
            type: object
            properties: {contact_id: {type: integer, description: 'ID of the excluded contact', example: 3991132}, email: {type: string, description: 'The email address that was excluded from the send', example: test@example.com}, reason: {type: string, enum: [format, mx, hard_bounce], description: "Why the contact was excluded from the campaign send:\n- `format`: the email address failed basic format validation\n- `mx`: the email domain has no valid MX records\n- `hard_bounce`: the address has a recorded hard bounce/blacklist event", example: hard_bounce}, description: {type: string, description: 'Bounce reason/comment. Only present for `hard_bounce` rows.', example: '550-5.1.1 The email account that you tried to reach does not exist.'}}
        InvalidContactsReport:
            type: object
            properties: {rows: {type: array, items: {$ref: '#/components/schemas/InvalidContactRow'}}}
        InvalidContactsResetResult:
            type: object
            properties: {success: {type: boolean, example: true}}
        CampaignKpiStats:
            type: object
            properties: {total_count: {type: integer, description: 'Total number of campaign events', example: 1250}, sent_count: {type: integer, description: 'Number of emails sent', example: 1200}, delivered_count: {type: integer, description: 'Number of emails delivered', example: 1180}, delivered_rate: {type: number, format: float, description: 'Delivery rate as percentage (0-100)', example: 98.3}, open_count: {type: integer, description: 'Number of unique emails opened', example: 305}, open_rate: {type: number, format: float, description: 'Open rate as percentage (0-100)', example: 25.4}, total_open_count: {type: integer, description: 'Total number of opens, including repeat opens by the same recipient', example: 412}, click_count: {type: integer, description: 'Number of unique clicks', example: 38}, click_rate: {type: number, format: float, description: 'Click rate as percentage (0-100)', example: 3.2}, total_click_count: {type: integer, description: 'Total number of clicks, including repeat clicks by the same recipient', example: 45}, performance_rating: {type: number, format: float, description: 'Performance rating as percentage (0-100)', example: 85.2}, unsubscribe_count: {type: integer, description: 'Number of unsubscribes', example: 10}, unsubscribe_rate: {type: number, format: float, description: 'Unsubscribe rate as percentage (0-100)', example: 0.8}, bounce_count: {type: integer, description: 'Number of bounces (hard and soft)', example: 20}, bounce_rate: {type: number, format: float, description: 'Bounce rate as percentage (0-100)', example: 1.7}, hard_bounce_count: {type: integer, description: 'Number of hard (permanent) bounces', example: 14}, hard_bounce_rate: {type: number, format: float, description: 'Hard bounce rate as percentage (0-100)', example: 1.2}, soft_bounce_count: {type: integer, description: 'Number of soft (temporary) bounces', example: 6}, soft_bounce_rate: {type: number, format: float, description: 'Soft bounce rate as percentage (0-100)', example: 0.5}, abuse_count: {type: integer, description: 'Number of abuse reports', example: 2}, abuse_rate: {type: number, format: float, description: 'Abuse rate as percentage (0-100)', example: 0.2}}
        CampaignsAggregateKpiStats:
            type: object
            properties: {group_id: {type: integer, description: 'ID of the group', example: 1}, goal: {type: string, description: 'Campaign goal filter used', example: announcement}, kpis: {type: object, properties: {campaigns_aggregate: {type: object, properties: {campaign_count: {type: integer, description: 'Number of campaigns in the last 30 days', example: 5}, total_emails_sent: {type: integer, description: 'Total number of emails sent in the last 30 days', example: 25000}, total_emails_delivered: {type: integer, description: 'Total number of emails delivered in the last 30 days', example: 23750}, average_open_rate: {type: number, format: float, description: 'Average open rate in the last 30 days as percentage (0-100)', example: 32.5}, average_click_rate: {type: number, format: float, description: 'Average click rate in the last 30 days as percentage (0-100)', example: 6.8}, campaign_count_diff: {type: number, format: float, description: 'Percentage difference in campaign count vs previous 3 months average', example: 15}, total_emails_sent_diff: {type: number, format: float, description: 'Percentage difference in emails sent vs previous 3 months average', example: 8.5}, total_emails_delivered_diff: {type: number, format: float, description: 'Percentage difference in emails delivered vs previous 3 months average', example: 9.2}, average_open_rate_diff: {type: number, format: float, description: 'Percentage difference in open rate vs previous 3 months average', example: -2.1}, average_click_rate_diff: {type: number, format: float, description: 'Percentage difference in click rate vs previous 3 months average', example: 1.3}}}}}}
        CampaignsAggregateKpiStatsEnhanced:
            type: object
            properties: {group_id: {type: integer, description: 'ID of the group', example: 1}, filters: {$ref: '#/components/schemas/CampaignFiltersApplied'}, kpis: {type: object, properties: {campaigns_aggregate: {type: object, properties: {campaign_count: {type: integer, description: 'Number of campaigns in the last 30 days', example: 5}, total_emails_sent: {type: integer, description: 'Total number of emails sent in the last 30 days', example: 25000}, total_emails_delivered: {type: integer, description: 'Total number of emails delivered in the last 30 days', example: 23750}, average_open_rate: {type: number, format: float, description: 'Average open rate in the last 30 days as percentage (0-100)', example: 32.5}, average_click_rate: {type: number, format: float, description: 'Average click rate in the last 30 days as percentage (0-100)', example: 6.8}, campaign_count_diff: {type: number, format: float, description: 'Percentage difference in campaign count vs previous 3 months average', example: 15}, total_emails_sent_diff: {type: number, format: float, description: 'Percentage difference in emails sent vs previous 3 months average', example: 8.5}, total_emails_delivered_diff: {type: number, format: float, description: 'Percentage difference in emails delivered vs previous 3 months average', example: 9.2}, average_open_rate_diff: {type: number, format: float, description: 'Percentage difference in open rate vs previous 3 months average', example: -2.1}, average_click_rate_diff: {type: number, format: float, description: 'Percentage difference in click rate vs previous 3 months average', example: 1.3}}}}}}
        CampaignFiltersApplied:
            type: object
            description: 'Applied filters for campaign aggregation'
            properties: {goal: {type: string, description: 'Campaign goal filter', example: announcement}, status: {type: string, description: 'Campaign status filter', example: started}, is_successful: {type: boolean, description: 'Campaign success filter', example: true}, campaign_name: {type: string, description: 'Campaign name search filter', example: 'Black Friday'}, started_at: {$ref: '#/components/schemas/DateRangeApplied'}, created_at: {$ref: '#/components/schemas/DateRangeApplied'}, contacts_selected: {$ref: '#/components/schemas/IntRangeApplied'}, performance_rating: {$ref: '#/components/schemas/NumberRangeApplied'}, sender_ids: {type: array, items: {type: integer}, description: 'List of sender IDs', example: [123, 456, 789]}, user_ids: {type: array, items: {type: integer}, description: 'List of user IDs', example: [100, 200, 300]}, saved_filter_ids: {type: array, items: {type: integer}, description: 'List of saved filter IDs', example: [50, 60, 70]}, template_ids: {type: array, items: {type: integer}, description: 'List of template IDs', example: [10, 20, 30]}}
        DateRangeApplied:
            type: object
            description: 'Date range filter'
            properties: {from: {type: string, format: date-time, description: 'Start date (inclusive)', example: '2024-01-01T00:00:00Z'}, to: {type: string, format: date-time, description: 'End date (inclusive)', example: '2024-12-31T23:59:59Z'}}
        IntRangeApplied:
            type: object
            description: 'Integer range filter'
            properties: {min: {type: integer, description: 'Minimum value (inclusive)', example: 100}, max: {type: integer, description: 'Maximum value (inclusive)', example: 10000}}
        NumberRangeApplied:
            type: object
            description: 'Number range filter'
            properties: {min: {type: number, format: float, description: 'Minimum value (inclusive)', example: 0.7}, max: {type: number, format: float, description: 'Maximum value (inclusive)', example: 1}}
        CampaignReply:
            type: object
            description: "Response for campaign send operations. Returns immediately with HTTP 202 Accepted\nfor asynchronous processing. Some fields are populated immediately while others\nare populated during background processing."
            properties: {steps: {type: array, items: {$ref: '#/components/schemas/EmailerCampaignStep'}, description: "Campaign processing steps for tracking progress. Initially empty in the immediate\nresponse, populated during background processing. Check campaign status for updates."}, filter: {$ref: '#/components/schemas/CampaignContactFilter', description: "Contact filtering results showing blacklisted, invalid, and duplicate contacts.\nPopulated after contact retrieval completes during background processing."}, in_progress: {$ref: '#/components/schemas/InProgress', description: "Queue information and throughput metrics for ElasticEmail campaigns.\nOnly populated for ElasticEmail campaigns, null for Brevo campaigns."}, links: {type: array, items: {$ref: '#/components/schemas/CampaignStepLink'}, description: "HATEOAS links for related resources. Always includes a link to the campaign\ndetails endpoint for monitoring progress."}, test: {type: boolean, description: "Indicates if this is a test campaign. True for campaigns sent to specific\nemail addresses, false for campaigns sent to saved filter audiences.", example: false}}
        CampaignContactFilter:
            type: object
            properties: {blacklisted_emails: {type: array, items: {type: integer}, description: 'IDs of blacklisted email contacts'}, invalid_emails: {type: array, items: {type: integer}, description: 'IDs of contacts with invalid emails'}, invalid_domains: {type: array, items: {type: integer}, description: 'IDs of contacts with invalid domains'}, duplicates: {type: array, items: {type: integer}, description: 'IDs of duplicate contacts'}, group_optins: {type: array, items: {type: integer}, description: 'IDs of contacts with group opt-ins'}}
        CampaignSearch:
            type: object
            properties: {status: {type: string, enum: [draft, pending, scheduled, running, rejected, cancelled, started, archived, failed], description: 'Filter by campaign status'}, subject: {type: string, description: 'Filter by subject (partial match)'}, reply_to: {type: string, description: 'Filter by reply-to address'}, campaign_name: {type: string, description: 'Filter by campaign name (partial match)'}, goal: {type: string, description: 'Filter by campaign goal'}, group_id: {type: integer, description: 'Filter by group ID'}, user_id: {type: integer, description: 'Filter by user ID'}, page: {type: integer, default: 1, description: 'Page number for pagination'}, per_page: {type: integer, default: 50, description: 'Number of items per page'}, is_successful: {type: boolean, description: 'Filter by campaign success status', example: true}, started_at_from: {type: string, format: date-time, description: 'Filter campaigns started from this date (inclusive)', example: '2024-01-01T00:00:00Z'}, started_at_to: {type: string, format: date-time, description: 'Filter campaigns started until this date (inclusive)', example: '2024-12-31T23:59:59Z'}, created_at_from: {type: string, format: date-time, description: 'Filter campaigns created from this date (inclusive)', example: '2024-01-01T00:00:00Z'}, created_at_to: {type: string, format: date-time, description: 'Filter campaigns created until this date (inclusive)', example: '2024-12-31T23:59:59Z'}, contacts_selected_min: {type: integer, minimum: 0, description: 'Minimum number of contacts selected', example: 100}, contacts_selected_max: {type: integer, minimum: 1, description: 'Maximum number of contacts selected', example: 10000}, performance_rating_min: {type: number, format: float, minimum: 0, maximum: 1, description: 'Minimum performance rating (0.0-1.0)', example: 0.7}, performance_rating_max: {type: number, format: float, minimum: 0, maximum: 1, description: 'Maximum performance rating (0.0-1.0)', example: 1}, sender_ids: {type: array, items: {type: integer}, description: 'List of sender IDs to filter by', example: [123, 456, 789]}, user_ids: {type: array, items: {type: integer}, description: 'List of user IDs to filter by', example: [100, 200, 300]}, saved_filter_ids: {type: array, items: {type: integer}, description: 'List of saved filter IDs to filter by', example: [50, 60, 70]}, template_ids: {type: array, items: {type: integer}, description: 'List of template IDs to filter by (replaces emailer_template_id)', example: [10, 20, 30]}, sender_id: {type: integer, description: 'Filter by sender ID (deprecated - use sender_ids instead)'}, saved_filter_id: {type: integer, description: 'Filter by saved filter ID (deprecated - use saved_filter_ids instead)'}, emailer_template_id: {type: integer, description: 'Filter by template ID (deprecated - use template_ids instead)'}, scheduled_at: {type: string, format: date-time, description: 'Filter by scheduled date (legacy - use started_at_from/to for date ranges)'}}
        RealizeCampaignArgs:
            type: object
            description: "Realizes a campaign template: substitutes Qomon's own merge tags (e.g.\n{{ firstname }}, {{ salutation }}) with their Brevo-native form (e.g.\n{{contact.FIRSTNAME}}, {{contact.CIVILITE}}) - it does NOT substitute real\nper-recipient data, since a campaign is rendered once for the whole\nbatch and Brevo resolves the actual value per recipient at send time.\nFor transactional templates rendered with real data instead, use\nPOST /emailer/template/realize."
            properties: {object: {type: string, description: 'Raw HTML template content to realize. Required unless emailer_template_id is provided.'}, subject: {type: string, description: 'Email subject line (also realized - merge tags substituted the same way as `object`).'}, emailer_template_id: {type: integer, description: 'ID of an existing email template to load and realize, instead of passing raw `object`/`subject`.'}, locale: {type: string, description: "Locale used to pick the right Brevo attribute names (e.g. GENRE/CIVILITE for \"fr\" vs\nGENDER/SALUTATION otherwise) and unsubscribe wording. Auto-resolved from the group's\nsettings if omitted."}, provider: {type: string, description: 'Email provider (e.g. "brevo"). Auto-resolved from the group''s configured provider if omitted.'}, campaign_id: {type: integer, description: 'Campaign ID. Used to build the per-campaign topic-unsubscribe link ({{altunsubscribe}}).'}, campaign_attributes: {$ref: '#/components/schemas/CampaignAttributes'}}
        CampaignAttributes:
            type: object
            description: "The real Brevo contact-attribute names for this group/locale (e.g.\n`{\"GENDER\":\"GENRE\",\"SALUTATION\":\"CIVILITE\",...}` for a French group).\nOptional - if omitted, POST /emailer/campaign/realize resolves this automatically\nfrom the group's Brevo account (the same resolution a real campaign send does),\nso callers normally never need to set this themselves."
            properties: {FIRSTNAME: {type: string}, LASTNAME: {type: string}, GENDER: {type: string}, SALUTATION: {type: string}, MARITAL_NAME: {type: string}, EMAIL: {type: string}, QOMON_ID: {type: string}}
        EmailerTemplate:
            type: object
            properties: {id: {type: integer, description: 'Unique identifier for the template', nullable: true}, name: {type: string, description: 'Name of the template'}, content: {type: string, description: 'HTML content of the template'}, created_at: {type: string, format: date-time, description: 'When the template was created'}, updated_at: {type: string, format: date-time, description: 'When the template was last updated'}, category: {$ref: '#/components/schemas/TemplateCategory'}}
        EmailerAccounts:
            type: object
            properties: {CreatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, UpdatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, DeletedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z', nullable: true}, id: {type: integer}, group_id: {type: integer}, credits: {type: integer}, provider_id: {type: integer}, provider: {$ref: '#/components/schemas/EmailerProvider'}, senders: {$ref: '#/components/schemas/EmailerSenders'}, domains: {type: array, items: {$ref: '#/components/schemas/EmailerDomain'}}, credits_refill: {$ref: '#/components/schemas/EmailerCreditRefill'}, subscription: {$ref: '#/components/schemas/EmailerSubscription'}, default_flag: {type: boolean}, sub_account_id: {type: integer, description: 'ID of the sub-account, can be used to know if account is legacy', nullable: true}}
        EmailerCreditRefill:
            type: object
            properties: {CreatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, DeletedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z', nullable: true}, UpdatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, account_id: {type: integer}, amount: {type: integer}, anniversary: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, id: {type: integer}, refill_day: {type: integer}, refill_next: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}, refill_until: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}}
        EmailerSubscription:
            type: object
            properties: {CreatedAt: {type: string, format: date-time, description: 'The timestamp when the subscription was created'}, UpdatedAt: {type: string, format: date-time, description: 'The timestamp when the subscription was last updated'}, DeletedAt: {type: string, format: date-time, description: 'The timestamp when the subscription was deleted (if soft-deleted)', nullable: true}, id: {type: integer, description: 'Unique identifier for the subscription'}, account_id: {type: integer, description: 'Identifier for the associated account'}, automatic_upsell: {type: boolean, description: 'Indicates whether automatic upsell is enabled'}, no_stripe: {type: boolean, description: 'Indicates whether the subscription uses Stripe for payment'}, pack_count: {type: integer, description: 'Number of credit packs in the subscription', example: 2}, free_credit_count: {type: integer, description: 'Number of free credits included in the subscription', example: 1000}}
        CreditManagementRequest:
            type: object
            required: [method, amount]
            properties: {method: {type: string, enum: [set, add], description: "The operation to perform on the account credits.\n- `set` - Set the total credit count to the specified amount\n- `add` - Add the specified amount to the current credit count\n", example: set}, amount: {type: integer, minimum: 1, description: 'The amount of credits to set or add', example: 10000}}
        CreditManagementResponse:
            type: object
            properties: {success: {type: boolean, description: 'Whether the operation was successful', example: true}, message: {type: string, description: 'Success or error message', example: 'Credits set successfully'}, method: {type: string, enum: [set, add], description: 'The method that was used', example: set}, amount: {type: integer, description: 'The amount that was processed', example: 10000}}
        EmailerDomain:
            type: object
            properties: {CreatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}}
        EmailerProvider:
            type: object
            properties: {CreatedAt: {type: string, format: date-time, example: '2023-12-19T14:51:49.258046Z'}}
        EmailerSenders:
            type: array
            items: {type: object, properties: {id: {type: integer, description: 'Unique identifier for the sender'}, account_id: {type: integer, description: 'Unique identifier for the associated account'}, domain_id: {type: integer, nullable: true, description: 'Unique identifier for the associated domain, if any'}, remote_id: {type: integer, description: 'Unique remote identifier for the sender'}, email: {type: string, description: 'Email address of the sender'}, name: {type: string, description: 'Display name of the sender'}, validated: {type: boolean, description: 'Indicates whether the sender''s email has been validated'}, removable: {type: boolean, description: 'Indicates whether the sender can be removed'}, default_flag: {type: boolean, description: 'Indicates whether this sender is the default sender'}}, required: [email, validated]}
        EmailerSender:
            type: object
            properties: {id: {type: integer, description: 'Unique identifier for the sender', nullable: true}, email: {type: string, description: 'Email address of the sender'}, name: {type: string, description: 'Display name of the sender'}, validated: {type: boolean, description: 'Indicates whether the sender''s email has been validated'}, created_at: {type: string, format: date-time, description: 'When the sender was created'}, updated_at: {type: string, format: date-time, description: 'When the sender was last updated'}}
        Polygon:
            type: array
            items: {type: object, properties: {lat: {type: number, example: 48.869476}, lng: {type: number, example: 2.02458}}}
        AdvancedSearchCondition:
            type: object
            additionalProperties: false
            properties: {$condition: {type: object, required: [attr, ope], properties: {attr: {type: string, example: surname, description: Attribute}, ope: {type: string, example: eql, description: Operator, enum: ['eql:strictdata', 'not_eql:strictdata', contains, not_contains, eql, not_eql, ext, not_ext, range, lte, gte, start_with, not_start_with]}, form_id: {type: integer, example: 4250, description: 'If attr is form, the form_id is required'}, form_ref_id: {type: array, description: 'If attr is form, the form_ref_id is required', items: {type: integer}, example: [6648]}, value: {type: string, description: 'Value of the attr or form_ref_id if necessary (depends of the fields and the operator)', nullable: true, example: Dupont}}}}
        AdvancedSearchNode:
            type: object
            additionalProperties: false
            oneOf: [{required: [$all]}, {required: [$at_least_one]}]
            properties: {$all: {type: array, items: {$ref: '#/components/schemas/AdvancedSearchCondition'}}, $at_least_one: {type: array, items: {$ref: '#/components/schemas/AdvancedSearchCondition'}}}
        AdvancedSearch:
            type: object
            required: [query]
            additionalProperties: false
            properties: {page: {type: integer, example: 0}, per_page: {type: integer, example: 50}, sort_asc: {type: boolean, example: true}, sort_attr: {type: string, example: surname}, query: {type: object, additionalProperties: false, oneOf: [{required: [$all]}, {required: [$at_least_one]}], properties: {$all: {type: array, items: {$ref: '#/components/schemas/AdvancedSearchNode'}}, $at_least_one: {type: array, items: {$ref: '#/components/schemas/AdvancedSearchNode'}}}}}
        ClassicSearch:
            type: object
            properties: {address_included: {description: 'Values can be retrieve from /search/addresses', type: array, items: {type: object, properties: {city: {type: string, description: 'Root occurence on /search/addresses response'}, empty: {type: boolean}, street: {type: string, description: 'Sub occurence on /search/addresses response'}}}}, contact_ids: {type: array, items: {type: integer}}, fields: {type: array, items: {type: object, properties: {'0': {type: string, description: 'Group ID'}, '1': {type: string}, '2': {type: string, description: 'Number of contacts in response', example: '50'}, '3': {type: string, description: 'Start at index', example: '100'}, '4': {type: string, description: Gender, enum: [m, f, a]}, '5': {type: string}, '6': {type: string, description: 'Integer corresponding to the age categories', example: '4'}, '7': {type: string}, '8': {type: string}, '9': {type: string, format: date-time, description: 'Last visit date'}, '10': {type: string, description: Email, enum: [SET, UNSET]}, '11': {type: string, description: Phone, enum: [SET, UNSET, ONLYMOBILE]}, '12': {type: string, description: 'Last visit status'}, '13': {type: string, description: 'Form response'}}}}, only_duplicates: {type: boolean}, polling_station_included: {type: array, items: {type: object, properties: {pollingstation: {type: string}}}}, polling_station_included_missing: {type: boolean}, polygon: {$ref: '#/components/schemas/Polygon'}, query: {type: string, description: 'Query string to search by name or surname'}, tags: {type: array, items: {type: string}}}
        Search:
            anyOf: [{$ref: '#/components/schemas/ClassicSearch'}, {properties: {advanced_search: {$ref: '#/components/schemas/AdvancedSearch'}, include_interactions: {type: boolean, example: true}}}]
        Search2:
            type: object
            properties: {fields: {type: array, items: {type: string}}, polygon: {type: array, items: {type: object}}, query: {type: string}, tags: {type: array, items: {type: string}}, address_included: {type: array, items: {type: string}}, polling_station_included: {type: array, items: {type: string}}, polling_station_included_missing: {type: boolean}, contact_ids: {type: array, items: {type: integer}}, advanced_search: {type: object, properties: {query: {type: object}, sort_attr: {type: string}, sort_asc: {type: boolean}, page: {type: integer}, per_page: {type: integer}}}}
        InProgress:
            type: object
            properties: {count: {type: integer, description: 'Number of contacts in progress'}, through_put_seconds: {type: number, format: float, description: 'Throughput in seconds per email', example: 2.5}}
        ErrorResponse:
            type: object
            properties: {status: {type: string, description: 'Error status.', example: error}, message: {type: string, description: 'Error message.', example: 'Invalid input parameters'}, code: {type: string, description: 'Error code.', example: validation_error}}
        SavedFilter:
            type: object
            properties: {id: {type: integer, description: 'Unique identifier for the saved filter', nullable: true}, title: {type: string, description: 'Title/name of the saved filter'}, payload: {type: string, description: 'JSON string containing the filter criteria'}, created_at: {type: string, format: date-time, description: 'When the filter was created'}, updated_at: {type: string, format: date-time, description: 'When the filter was last updated'}}
        CampaignBasicStats:
            type: object
            description: 'Basic campaign statistics focused on essential metrics'
            properties: {campaign_id: {type: integer, description: 'Unique identifier of the campaign', example: 123}, delivered_count: {type: integer, description: 'Number of emails delivered for this campaign', example: 950}, open_count: {type: integer, description: 'Number of unique emails opened', example: 350}, open_rate: {type: number, format: float, description: 'Email open rate as percentage (0-100) based on delivered emails', minimum: 0, maximum: 100, example: 35}, total_open_count: {type: integer, description: 'Total number of opens, including repeat opens by the same recipient', example: 470}, click_count: {type: integer, description: 'Number of unique clicks', example: 50}, click_rate: {type: number, format: float, description: 'Email click rate as percentage (0-100) based on delivered emails', minimum: 0, maximum: 100, example: 5}, total_click_count: {type: integer, description: 'Total number of clicks, including repeat clicks by the same recipient', example: 58}, performance_rating: {type: number, format: float, description: 'Campaign performance rating (0-100) based on open and click rates', minimum: 0, maximum: 100, example: 85.2}}
        BatchCampaignBasicStatsResponse:
            type: object
            description: 'Response containing basic statistics for multiple campaigns'
            properties: {group_id: {type: integer, description: 'ID of the group the campaigns belong to', example: 1}, stats: {type: array, description: 'Array of campaign statistics', items: {$ref: '#/components/schemas/CampaignBasicStats'}}, not_found: {type: array, description: 'Array of campaign IDs that were not found in the specified group', items: {type: integer}, example: [999, 1000], nullable: true}}
        SendEmailRequest:
            type: object
            properties: {email: {type: object, properties: {object: {type: string, format: html, description: 'HTML content of the email.', example: '<!DOCTYPE html><html><body>...</body></html>'}, subject: {type: string, description: 'Subject line of the email.', example: 'Test sendemail'}, sender: {type: string, format: email, description: 'Email address of the sender.', example: nicolas@qomon.com}, sender_name: {type: string, description: 'Display name of the sender.', example: 'My Company'}, reply_to: {type: string, format: email, description: 'Reply-to email address.', example: support@qomon.com}, cc: {type: string, format: email, description: 'CC email addresses (comma-separated).', example: admin@qomon.com}, scheduled_at: {type: string, format: date-time, description: 'When to schedule the email (optional).', example: '2024-01-15T10:00:00Z'}, contacts: {type: array, items: {type: object, properties: {id: {type: integer, description: 'Contact ID'}, email: {type: string, format: email, description: 'Contact email address'}}}, description: 'Direct contact list for test emails (optional).'}}}, search: {type: object, description: 'Search criteria to select contacts (optional if contacts provided).', properties: {Search: {$ref: '#/components/schemas/Search2'}}}, emailer_template: {type: object, properties: {id: {type: integer, description: 'Template ID to link emailer template.', example: 42}, locale: {type: string, description: 'Template locale.', example: en, default: en}}}}
        SendEmailResponse:
            type: object
            properties: {status: {type: string, description: 'Status of the response.', example: success}, data: {type: object, properties: {in_progress: {$ref: '#/components/schemas/InProgress', description: 'Progress information for email sending.'}}}}
        Email:
            type: object
            properties: {id: {type: integer, description: 'Email ID'}, created: {type: string, format: date-time, description: 'When the email was created'}, sender: {type: string, format: email, description: 'Sender email address'}, sender_name: {type: string, description: 'Sender display name'}, subject: {type: string, description: 'Email subject'}, object: {type: string, format: html, description: 'HTML content of the email'}, receiver: {type: string, format: email, description: 'Recipient email address'}, group_id: {type: integer, description: 'Group ID'}, provider: {type: string, description: 'Email provider (brevo, elasticemail)'}, status: {type: string, description: 'Email status'}, error: {type: string, description: 'Error message if any'}, campaign_id: {type: integer, nullable: true, description: 'Associated campaign ID'}, scheduled_at: {type: string, format: date-time, nullable: true, description: 'Scheduled send time'}, test: {type: boolean, nullable: true, description: 'Whether this is a test email'}}
        SendEmailProgressResponse:
            type: object
            properties: {status: {type: string, description: 'Status of the response.', example: success}, data: {type: object, properties: {email: {type: string, nullable: true, description: 'Email processing result.'}, in_progress: {type: object, properties: {count: {type: integer, description: 'Number of emails still in progress.', example: 0}}}}}}
        EmailContacts:
            type: object
            properties: {original: {type: integer}, valids: {type: integer}, blacklisted: {type: integer}, no_consent: {type: integer}, invalid_mx: {type: integer}, duplicate: {type: integer}, no_email: {type: integer}, group_optin: {type: integer}, email_blacklisted: {type: integer}}
        EmailerTemplateResponse:
            type: object
            required: [status, data]
            properties: {status: {type: string, enum: [success]}, data: {type: object, required: [emailer_template], properties: {emailer_template: {$ref: '#/components/schemas/EmailerTemplate'}}}}
        EmailerTemplatesResponse:
            type: object
            required: [status, data]
            properties: {status: {type: string, enum: [success]}, data: {type: object, required: [emailer_templates], properties: {emailer_templates: {type: array, items: {$ref: '#/components/schemas/EmailerTemplate'}}}}}
        CreateTemplateRequest:
            type: object
            required: [data]
            properties: {data: {type: object, required: [emailer_template], properties: {emailer_template: {type: object, required: [name, content_html, group_id], properties: {name: {type: string, description: 'Name of the template', example: 'My New Template'}, content_html: {type: string, description: 'HTML content of the template', example: '<!DOCTYPE html><html><body><h1>Hello World</h1></body></html>'}, content_json: {type: string, description: 'JSON content of the template (optional)'}, group_id: {type: integer, description: 'ID of the group the template belongs to', example: 1}, folder_id: {type: integer, description: 'Optional folder ID to organize templates', nullable: true, example: 5}, locale: {type: string, description: 'Locale of the template', example: en, default: en}, category: {$ref: '#/components/schemas/TemplateCategory'}}}}}}
        UpdateTemplateRequest:
            type: object
            required: [data]
            properties: {data: {type: object, required: [emailer_template], properties: {emailer_template: {type: object, required: [id], properties: {id: {type: integer, description: 'ID of the template to update', example: 88888}, name: {type: string, description: 'Name of the template', example: 'Updated Template Name'}, content_html: {type: string, description: 'HTML content of the template'}, content_json: {type: string, description: 'JSON content of the template'}, folder_id: {type: integer, description: 'Folder ID to organize templates', nullable: true}, locale: {type: string, description: 'Locale of the template'}, is_favorite: {type: boolean, description: 'Whether the template is marked as favorite'}, category: {$ref: '#/components/schemas/TemplateCategory'}}}}}}
        RealizeTemplateArgs:
            type: object
            required: [emailer_template_id]
            properties: {emailer_template_id: {type: integer, description: 'ID of the template to realize', example: 42}, map_models: {type: object, description: 'Map of models for template variables', additionalProperties: true, example: {contact: {firstname: John, lastname: Doe, email: john.doe@example.com}}}}
        TemplateVariable:
            type: object
            description: 'A single merge-tag usable inside email templates.'
            properties: {tag: {type: string, description: 'Canonical form to insert, e.g. "firstname" or "lineAddress1".', example: firstname}, nested_tag: {type: string, description: "Dot-notation form when available, e.g. \"contact.firstname\" - this is also the\n`map_models` path this variable is resolved from.", example: contact.firstname}, label: {type: string, description: 'Human label for this tag, localized server-side to the requested locale.', example: 'First name'}, type: {type: string, enum: [string, number, boolean, date, url, email, phone], description: "Coarse frontend-facing hint for how to render/format this tag's value. For `source: model`\nvariables it's inferred from the registered struct's field type; for `source: prelude`/\n`computed` variables (no struct field to infer from) it is authored by hand in the catalogue.", example: string}, value: {type: string, description: "The tag resolved against the `map_models` supplied to POST /emailer/template/variables.\nAlways empty/absent on the GET catalogue. Variables whose backing data wasn't present in\n`map_models` resolve to an empty string but remain listed (`type` still set)."}, template_types: {type: array, items: {type: string}, description: "Restricts the tag to given template types (event/petition/online-form/campaign).\nEmpty/absent means it applies everywhere the group applies."}}
        TemplateVariableGroup:
            type: object
            description: 'A group of merge-tags by source model/category.'
            properties: {key: {type: string, example: contact}, label: {type: string, description: 'Human label for this group, localized server-side to the requested locale.', example: Contact}, source: {type: string, enum: [model, prelude, computed], description: '`model` = struct field; `prelude` = computed by the liquid prelude; `computed` = resolved server-side outside those two, e.g. synced as its own Brevo contact attribute (not a raw model field).'}, template_types: {type: array, items: {type: string}, description: "The template types this group applies to (see the `template_type` query/body parameter).\nThe catalogue always lists this explicitly, even for tags usable everywhere (e.g.\nfirstname lists all 4 known types) - it is never omitted to mean \"universal\", so\nyou can tell at a glance whether a given tag works for campaigns.", example: [event, petition, online-form, campaign]}, variables: {type: array, items: {$ref: '#/components/schemas/TemplateVariable'}}}
        TemplateVariablesReply:
            type: object
            description: "The merge-tag catalogue, shared by GET and POST /emailer/template/variables. GET never sets\n`value` on any variable (no backend-authored examples); POST resolves `value` against the\n`map_models` supplied in its request body."
            properties: {groups: {type: array, description: 'Variables grouped by source model/category.', items: {$ref: '#/components/schemas/TemplateVariableGroup'}}}
        TemplateVariablesRealizeArgs:
            type: object
            properties: {locale: {type: string, description: "Locale to resolve labels and values in (e.g. \"en\", \"fr\"). Falls back to the group's own\nlocale, then to \"en\", when omitted.", example: fr}, template_type: {type: string, enum: [campaign, event, petition, online-form], description: "Restricts the returned groups/variables to those usable for this template type. A group or\nvariable with no `template_types` restriction of its own is returned regardless of this\nfilter (it applies everywhere). Omit to get the full, unfiltered catalogue."}, map_models: {type: object, description: "The same `map_models` shape POSTed to /emailer/template/realize (e.g. real contact/action/site\ndata taken from the group) - each catalogued variable is resolved against it. Variables whose\nbacking data isn't present here resolve to an empty value but stay in the list.", additionalProperties: true, example: {contact: {firstname: Jane, surname: Doe}}}}
        TemplateCategory:
            type: string
            enum: [email, campaign, transac, workflow, workflow_message]
            description: "Category of the template. Note: Templates with category='workflow_message' are internal \nworkflow message templates and are not returned by GET /emailer/templates endpoint.\n"
            example: email
            default: email
    examples:
        SendEmailTestExample:
            summary: 'Example of a test email request (direct contact list)'
            value: {email: {object: '<!DOCTYPE html><html><body><h1>Test Email</h1><p>This is a test email.</p></body></html>', subject: 'Test Email', sender: test@qomon.com, sender_name: 'Test Sender', reply_to: support@qomon.com, contacts: [{email: recipient1@example.com}, {email: recipient2@example.com}]}}
        SendEmailsWithSearchExample:
            summary: 'Example of transactional emails with search request'
            value: {email: {object: '<!DOCTYPE html><html><body><h1>Newsletter</h1><p>Welcome to our newsletter!</p></body></html>', subject: 'Monthly Newsletter', sender: newsletter@qomon.com, sender_name: 'Newsletter Team', reply_to: unsubscribe@qomon.com, scheduled_at: '2024-01-15T10:00:00Z'}, search: {Search: {fields: ['1', fullname, '1', '0', "", "", "", surname, 'true', "", "", ""], polygon: [], query: 'newsletter subscribers', tags: [newsletter], address_included: [], polling_station_included: [], polling_station_included_missing: false, contact_ids: [4118849, 4118850], advanced_search: {query: {$all: [{$all: []}, {$all: [{$at_least_one: [{$condition: {attr: firstname, ope: eql, value: John}}, {$condition: {attr: surname, ope: eql, value: Doe}}]}]}]}, sort_attr: surname, sort_asc: true, page: 0, per_page: 100}}}, emailer_template: {id: 42, locale: en}}
        SendEmailTestResponseExample:
            summary: 'Example response for email sending (returns progress information)'
            value: {status: success, data: {in_progress: {count: 150, through_put_seconds: 2.5}}}
        SendEmailProgressResponseExample:
            summary: 'Example response for email with progress tracking'
            value: {status: success, data: {Email: null, in_progress: {count: 150, through_put_seconds: 2.5}}}
        EmailContactsExampleRequest:
            summary: 'Example request for email contacts KPI'
            value: {data: {search: {fields: ['1', all, '27', '0', "", "", "", surname, 'true', "", "", ""], polygon: [], query: "", tags: [], address_included: [], polling_station_included: [], polling_station_included_missing: false, advanced_search: {query: {$all: [{$all: [{$condition: {attr: mail, ope: start_with, value: hugo+}}, {$condition: {attr: "", ope: "", value: ""}}]}]}, sort_attr: surname, sort_asc: true, page: 0, per_page: 27}, contact_ids: [4118849]}}}
        EmailContactsExampleResponse:
            summary: 'Example response for successful email contacts retrieval'
            value: {status: success, data: {original: 6, valids: 1, no_consent: 2, duplicate: 3, no_email: 1, email_blacklisted: 2}}
        EmailerTemplateExample:
            summary: 'Example response for a single email template'
            value: {status: success, data: {emailer_template: {id: 88888, group_id: 1, user_id: 42, CreatedAt: '2023-10-18T13:51:39.156291Z', UpdatedAt: '2024-11-19T12:56:27.505651Z', name: 'New test template updated', content_html: '<!DOCTYPE html><html><body><h1>Hello World</h1></body></html>', content_json: '{"page":{"body":{"container":{"style":{"background-color":"transparent"}}}}', image_file_id: 888888, image_url: 'https://file-qomon.s3.amazonaws.com/template_88888.jpeg', url: /emailer/template/88888, is_favorite: false, locale: en, category: email}}}
        BatchCampaignBasicStatsSuccessExample:
            summary: 'Example response for successful batch campaign basic stats (all campaigns found)'
            value: {status: success, data: {group_id: 1, stats: [{campaign_id: 123, delivered_count: 950, open_count: 350, open_rate: 35, total_open_count: 470, click_count: 50, click_rate: 5, total_click_count: 58, performance_rating: 85.2}, {campaign_id: 456, delivered_count: 1900, open_count: 600, open_rate: 30, total_open_count: 810, click_count: 80, click_rate: 4, total_click_count: 95, performance_rating: 78.4}, {campaign_id: 789, delivered_count: 1450, open_count: 450, open_rate: 30, total_open_count: 605, click_count: 75, click_rate: 5, total_click_count: 88, performance_rating: 80}]}}
        BatchCampaignBasicStatsPartialExample:
            summary: 'Example response when some campaigns are not found (replication lag scenario)'
            value: {status: success, data: {group_id: 1, stats: [{campaign_id: 123, delivered_count: 950, open_count: 350, open_rate: 35, total_open_count: 470, click_count: 50, click_rate: 5, total_click_count: 58, performance_rating: 85.2}, {campaign_id: 456, delivered_count: 1900, open_count: 600, open_rate: 30, total_open_count: 810, click_count: 80, click_rate: 4, total_click_count: 95, performance_rating: 78.4}], not_found: [789, 101112]}}
        CampaignExampleResponse:
            summary: 'Example response for a single campaign'
            value: {status: success, data: {id: 198, goal: announcement, campaign_name: 'Product Launch Campaign', sender: {id: 123, email: sender@qomon.com, name: 'Marketing Team', validated: true}, template: {id: 456, name: 'Product Launch Template', content: '<!DOCTYPE html><html><body><h1>New Product Launch</h1></body></html>'}, user_id: 42, group_id: 1, status: started, contacts_selected: 5000, created_at: '2024-01-15T10:00:00Z', started_at: '2024-01-15T14:00:00Z', performance_rating: 0.85, is_successful: true}}
        CampaignExampleResponseArchived:
            summary: 'Example response for a single campaign (archived)'
            value: {status: success, data: {id: 198, goal: announcement, campaign_name: 'Product Launch Campaign', sender: {id: 123, email: sender@qomon.com, name: 'Marketing Team', validated: true}, template: {id: 456, name: 'Product Launch Template', content: '<!DOCTYPE html><html><body><h1>New Product Launch</h1></body></html>'}, user_id: 42, group_id: 1, status: archived, contacts_selected: 5000, created_at: '2024-01-15T10:00:00Z', started_at: '2024-01-15T14:00:00Z', performance_rating: 0.85, is_successful: true, delivered_count: 1000, open_count: 500, unique_click_count: 100, hard_bounce_count: 10, unsubscribe_count: 10, spam_complaints_count: 10}}
        CampaignsAggregateNoFiltersExample:
            summary: 'Example response with no filters applied'
            value: {status: success, data: {group_id: 1, filters: {}, kpis: {campaigns_aggregate: {campaign_count: 12, total_emails_sent: 58000, total_emails_delivered: 55100, average_open_rate: 28.7, average_click_rate: 5.2, campaign_count_diff: 25.5, total_emails_sent_diff: 12.3, total_emails_delivered_diff: 14.8, average_open_rate_diff: 3.4, average_click_rate_diff: -1.8}}}}
        CampaignsAggregateBasicFiltersExample:
            summary: 'Example response with basic filters (goal and status)'
            value: {status: success, data: {group_id: 1, filters: {goal: announcement, status: started}, kpis: {campaigns_aggregate: {campaign_count: 5, total_emails_sent: 25000, total_emails_delivered: 23750, average_open_rate: 32.5, average_click_rate: 6.8, campaign_count_diff: 15, total_emails_sent_diff: 8.5, total_emails_delivered_diff: 9.2, average_open_rate_diff: -2.1, average_click_rate_diff: 1.3}}}}
        CampaignsAggregateComplexFiltersExample:
            summary: 'Example response with complex filters including date ranges, lists, and performance criteria'
            value: {status: success, data: {group_id: 1, filters: {goal: newsletter, status: started, campaign_name: weekly, started_at: {from: '2024-01-01T00:00:00Z', to: '2024-12-31T23:59:59Z'}, contacts_selected: {min: 1000, max: 50000}, performance_rating: {min: 0.8}, sender_ids: [123, 456, 789], user_ids: [100, 200], template_ids: [10, 20, 30], is_successful: true}, kpis: {campaigns_aggregate: {campaign_count: 3, total_emails_sent: 18500, total_emails_delivered: 18200, average_open_rate: 38.2, average_click_rate: 8.4, campaign_count_diff: 50, total_emails_sent_diff: 22.3, total_emails_delivered_diff: 24.1, average_open_rate_diff: 5.7, average_click_rate_diff: 3.2}}}}
        EmailerTemplatesExample:
            summary: 'Example response for multiple email templates'
            value: {status: success, data: {emailer_templates: [{id: 88888, group_id: 1, user_id: 42, CreatedAt: '2023-10-18T13:51:39.156291Z', UpdatedAt: '2024-11-19T12:56:27.505651Z', name: 'Newsletter Template', content_html: '<!DOCTYPE html><html><body><h1>Newsletter</h1></body></html>', image_file_id: 888888, image_url: 'https://file-qomon.s3.amazonaws.com/template_88888.jpeg', url: /emailer/template/88888, is_favorite: true, locale: en, category: email}, {id: 88889, group_id: 1, user_id: 42, CreatedAt: '2023-10-19T14:22:15.123456Z', UpdatedAt: '2024-11-20T09:15:33.789012Z', name: 'Event Invitation', content_html: '<!DOCTYPE html><html><body><h1>You''re Invited!</h1></body></html>', image_file_id: 888889, image_url: 'https://file-qomon.s3.amazonaws.com/template_88889.jpeg', url: /emailer/template/88889, is_favorite: false, locale: fr, category: email}]}}
        CreateTemplateExample:
            summary: 'Example request to create a new template'
            value: {data: {emailer_template: {name: 'My New Template', content_html: '<!DOCTYPE html><html><body><h1>Hello World</h1><p>This is my new template</p></body></html>', content_json: '{"page":{"body":{"container":{"style":{"background-color":"#ffffff"}}}}}', group_id: 1, folder_id: 5, locale: en, category: email}}}
        UpdateTemplateExample:
            summary: 'Example request to update an existing template'
            value: {data: {emailer_template: {id: 88888, name: 'Updated Template Name', content_html: '<!DOCTYPE html><html><body><h1>Updated Content</h1></body></html>', content_json: '{"page":{"body":{"container":{"style":{"background-color":"#f0f0f0"}}}}}', folder_id: 7, is_favorite: true, category: email}}}
        SingleTemplateExample:
            summary: 'Example response for a single created template'
            value: {status: success, data: {emailer_template: {id: 88889, group_id: 1, user_id: 42, CreatedAt: '2025-04-09T15:10:00.000Z', UpdatedAt: '2025-04-09T15:10:00.000Z', name: 'My New Template', content_html: '<!DOCTYPE html><html><body><h1>Hello World</h1></body></html>', image_file_id: 888889, image_url: 'https://file-qomon.s3.amazonaws.com/new_image.jpeg', url: /emailer/template/88889, is_favorite: false, locale: en, category: email}}}
        TmpThumbnailRequestExample:
            summary: 'Example request for temporary thumbnail generation'
            value: {data: {emailer_template: {group_id: 1, content_html: '<!DOCTYPE html><html><body><h1>Test Template</h1><p>Content for thumbnail generation</p></body></html>'}}}
        TmpThumbnailExampleResponse:
            summary: 'Example response for temporary thumbnail'
            value: {status: success, data: {base64_thumbnail: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/...'}}
        EmailerSendersExampleResponse:
            summary: 'Example response for emailer senders list'
            value: {status: success, data: {senders: [{id: 123, account_id: 1, domain_id: 45, remote_id: 678, email: sender1@qomon.com, name: 'Marketing Team', validated: true, removable: true, default_flag: true}, {id: 124, account_id: 1, domain_id: 45, remote_id: 679, email: support@qomon.com, name: 'Support Team', validated: true, removable: false, default_flag: false}]}}
        CampaignsAggregateKpiStatsExample:
            summary: 'Example response for campaigns aggregate KPI statistics with specific goal'
            value: {status: success, data: {group_id: 1, goal: announcement, kpis: {campaigns_aggregate: {campaign_count: 5, total_emails_sent: 25000, total_emails_delivered: 23750, average_open_rate: 32.5, average_click_rate: 6.8, campaign_count_diff: 15, total_emails_sent_diff: 8.5, total_emails_delivered_diff: 9.2, average_open_rate_diff: -2.1, average_click_rate_diff: 1.3}}}}
        CampaignsAggregateAllGoalsExample:
            summary: 'Example response for campaigns aggregate KPI statistics with all goals'
            value: {status: success, data: {group_id: 1, goal: all, kpis: {campaigns_aggregate: {campaign_count: 12, total_emails_sent: 58000, total_emails_delivered: 55100, average_open_rate: 28.7, average_click_rate: 5.2, campaign_count_diff: 25.5, total_emails_sent_diff: 12.3, total_emails_delivered_diff: 14.8, average_open_rate_diff: 3.4, average_click_rate_diff: -1.8}}}}
        CreditManagementSetExample:
            summary: 'Example request to set credits to 10,000'
            value: {method: set, amount: 10000}
        CreditManagementAddExample:
            summary: 'Example request to add 5,000 credits'
            value: {method: add, amount: 5000}
        CreditManagementSetResponseExample:
            summary: 'Example response for successfully setting credits'
            value: {status: success, data: {success: true, message: 'Credits set successfully', method: set, amount: 10000}}
        CreditManagementAddResponseExample:
            summary: 'Example response for successfully adding credits'
            value: {status: success, data: {success: true, message: 'Credits added successfully', method: add, amount: 5000}}
        CreditManagementErrorExample:
            summary: 'Example error response for invalid amount'
            value: {status: error, message: 'amount must be greater than 0'}
