Docuseal Submission Completion Processing: A Step-by-Step Code Analysis
When a submitter finishes a Docuseal form, the ProcessSubmitterCompletionJob Sidekiq job orchestrates PDF generation, audit trails, email notifications, and webhook deliveries to finalize the submission.
In the docusealco/docuseal open-source document signing platform, submission completion processing transforms raw form data into finalized, auditable records. This critical workflow runs entirely in the background to keep the web interface responsive while handling document generation, persistence, and external integrations. Understanding this pipeline is essential for developers customizing notifications, debugging webhook delivery, or scaling the application.
Orchestration Architecture
All submission completion processing happens inside ProcessSubmitterCompletionJob, located at app/jobs/process_submitter_completion_job.rb. This Sidekiq background job receives a submitter_id parameter and executes an eleven-step pipeline that converts a submitter's finalized data into permanent artifacts.
The job maintains strict separation of concerns by delegating heavy PDF manipulation to service objects in lib/submissions/, while handling database persistence and notification orchestration itself. This architecture ensures that HTTP requests return immediately, with the heavy lifting deferred to background workers.
Step-by-Step Execution Flow
Loading and Snapshot Creation
The job begins by loading the submitter record and creating a durable snapshot of the completion state. At lines 7-8 in app/jobs/process_submitter_completion_job.rb, the job fetches the record:
submitter = Submitter.find(params['submitter_id'])
Immediately after, the create_completed_submitter! method (lines 34-68) ensures a CompletedSubmitter row exists. This table stores immutable metadata about the final submission state, including the verification method and SMS usage counts. The implementation uses find_or_initialize_by and rescues ActiveRecord::RecordNotUnique (line 68) to guarantee idempotency during race conditions.
PDF Generation Pipeline
The system generates three distinct PDF artifacts depending on configuration and submission state:
-
Result PDF: The
Submissions::EnsureResultGeneratedservice (called at line 13) merges the submitter’s filled fields into the template PDF, creating the final signed document. -
Combined PDF: When all submitters for a submission are complete (determined at line 11 via
is_all_completed), and the account has enabledAccountConfig::COMBINE_PDF_RESULT_KEY, the job callsSubmissions::EnsureCombinedGenerated(lines 15-18) to create a single PDF containing every submitter's results. -
Audit-Trail PDF: Regardless of other settings, the job always generates a tamper-evident audit trail by calling
Submissions::EnsureAuditGeneratedat lines 20-21.
# From ProcessSubmitterCompletionJob#perform
is_all_completed = submission.submitters.all?(&:completed_at?)
Submissions::EnsureResultGenerated.call(submitter)
if is_all_completed && account_config.combine_pdf_result_enabled?
Submissions::EnsureCombinedGenerated.call(submission)
end
Submissions::EnsureAuditGenerated.call(submission)
Document Persistence
For each attachment with a SHA-256 hash, the job creates or retrieves a CompletedDocument record via create_completed_documents! (lines 72-78). This deduplication mechanism stores document references by hash, preventing duplicate storage of identical files across multiple submissions.
Completion Detection and Sequential Workflows
The job checks whether the current submitter represents the final pending signature (line 11). If not all submitters are finished and the submission uses preserved ordering (submitters_order == 'preserved'), the job triggers the next participant via enqueue_next_submitter_request_notification (lines 27-28 and method implementation at lines 60-92).
Notification and Webhook Delivery
Once artifacts are generated, the notification pipeline executes in two phases:
Email Notifications: The enqueue_completed_emails method (lines 22-23 and 30-27) builds recipient lists based on account settings, including owners, BCC addresses, and copy-email configurations. It dispatches SubmitterMailer.completed_email to each recipient.
Webhook Requests: The enqueue_completed_webhooks method (lines 80-99) iterates through account webhooks listening for form.completed or submission.completed events, queuing SendFormCompletedWebhookRequestJob or SendSubmissionCompletedWebhookRequestJob for asynchronous delivery.
Idempotency and Error Handling
The submission completion processing implements several safeguards against race conditions and duplicate processing:
- Record uniqueness: The
create_completed_submitter!method handlesActiveRecord::RecordNotUniqueexceptions to survive concurrent job executions. - Conditional PDF generation: Combined PDF creation only occurs when
is_all_completedis true, preventing partial artifacts. - Automatic retries: Sidekiq handles transient failures automatically according to the configured retry policy, ensuring eventual consistency.
Triggering the Completion Process
When a user submits their final field, the controller enqueues the background job. While the frontend posts signature data to the submitter completion endpoint, the background job is initiated with:
# Simplified example from the submission flow
submitter.complete!
ProcessSubmitterCompletionJob.perform_async(
'submitter_id' => submitter.id,
'send_invitation_email' => true
)
The controller at app/controllers/submissions_controller.rb preloads submission data for the UI, checking completion status before rendering:
def show
@submission = Submissions.preload_with_pages(@submission)
unless @submission.submitters.all?(&:completed_at?)
ActiveRecord::Associations::Preloader.new(
records: [@submission],
associations: [{ submitters: :start_form_submission_events }]
).call
end
render :show, layout: 'plain'
end
Summary
- Background Processing: The
ProcessSubmitterCompletionJobinapp/jobs/process_submitter_completion_job.rbhandles all submission completion processing asynchronously via Sidekiq. - PDF Generation: The system produces result PDFs, optional combined PDFs (when
AccountConfig::COMBINE_PDF_RESULT_KEYis enabled), and mandatory audit-trail PDFs through dedicated service objects. - Data Persistence:
CompletedSubmittersnapshots andCompletedDocumentrecords provide immutable references to finalized states and deduplicated file storage. - Notification Pipeline: Email delivery and webhook requests (
SendFormCompletedWebhookRequestJob/SendSubmissionCompletedWebhookRequestJob) execute only after successful artifact generation. - Sequential Support: When submitter order is preserved and submissions remain incomplete, the job automatically queues invitations for the next participant.
Frequently Asked Questions
What happens if the ProcessSubmitterCompletionJob fails mid-execution?
The job is designed to be idempotent. It uses find_or_initialize_by patterns and rescues ActiveRecord::RecordNotUnique exceptions (line 68) to handle race conditions gracefully. Sidekiq automatically retries failed jobs according to the configured retry policy, ensuring that temporary network issues or database locks do not result in lost completion data.
Where are the generated PDFs stored during submission completion processing?
The PDF generation logic resides in lib/submissions/ (specifically ensure_result_generated.rb, ensure_combined_generated.rb, and ensure_audit_generated.rb). These service objects write to the configured Active Storage backend. The resulting file references are stored in CompletedDocument records indexed by SHA-256 hash for deduplication.
How does Docuseal handle multiple submitters in sequential signing workflows?
When a submission has multiple submitters with preserved ordering, the job checks is_all_completed at line 11. If additional submitters remain pending, enqueue_next_submitter_request_notification (lines 27-28) automatically sends invitation emails to the next participant in sequence. Combined PDF generation is deferred until all submitters have completed their portions.
Can I disable specific notifications during the completion process?
Yes. The notification pipeline respects account configuration settings. The enqueue_completed_emails method checks for documents_copy_email_enabled and other preferences before dispatching SubmitterMailer.completed_email. Similarly, webhooks only fire for URLs explicitly configured in the account webhook settings for form.completed or submission.completed events.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →