diff --git a/app/controllers/notifications_settings_controller.rb b/app/controllers/notifications_settings_controller.rb
index 63aa5348..3e867ae8 100644
--- a/app/controllers/notifications_settings_controller.rb
+++ b/app/controllers/notifications_settings_controller.rb
@@ -4,6 +4,7 @@ class NotificationsSettingsController < ApplicationController
before_action :load_bcc_config, only: :index
before_action :load_reminder_config, only: :index
before_action :load_pending_reminders, only: :index
+ before_action :load_paperless_status, only: :index
authorize_resource :bcc_config, only: :index
authorize_resource :reminder_config, only: :index
@@ -73,6 +74,12 @@ class NotificationsSettingsController < ApplicationController
@pending_reminders.sort_by! { |r| r[:next_at] }
end
+ def load_paperless_status
+ @paperless_status = Rails.cache.fetch('paperless_ngx_health_check', expires_in: 60.seconds) do
+ Submissions::UploadToPaperless.health_check
+ end
+ end
+
def email_config_params
params.require(:account_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
attrs[:key] = nil unless attrs[:key].in?([AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMINDERS])
diff --git a/app/jobs/upload_to_paperless_job.rb b/app/jobs/upload_to_paperless_job.rb
index 37847054..a56fbb90 100644
--- a/app/jobs/upload_to_paperless_job.rb
+++ b/app/jobs/upload_to_paperless_job.rb
@@ -16,7 +16,14 @@ class UploadToPaperlessJob
attempt = params['attempt'].to_i
- Submissions::UploadToPaperless.call(submission)
+ Rails.logger.info("[Paperless-ngx] Uploading documents for submission #{submission.id}")
+
+ results = Submissions::UploadToPaperless.call(submission)
+
+ if results
+ Rails.logger.info("[Paperless-ngx] Upload complete for submission #{submission.id}: " \
+ "#{results.size} document(s), task IDs: #{results.join(', ')}")
+ end
rescue Submissions::UploadToPaperless::UploadError, Faraday::Error => e
return if attempt >= MAX_ATTEMPTS
diff --git a/app/views/notifications_settings/_paperless_status.html.erb b/app/views/notifications_settings/_paperless_status.html.erb
new file mode 100644
index 00000000..c30bd230
--- /dev/null
+++ b/app/views/notifications_settings/_paperless_status.html.erb
@@ -0,0 +1,28 @@
+
+
+ Paperless-ngx
+
+
+ <% if !@paperless_status[:configured] %>
+ Not Configured
+
+ Set PAPERLESS_NGX_URL and PAPERLESS_NGX_TOKEN environment variables to enable.
+
+ <% elsif @paperless_status[:reachable] %>
+ Connected
+
+ <%= @paperless_status[:url] %>
+
+ <% else %>
+ Unreachable
+
+ <%= @paperless_status[:url] %> — <%= @paperless_status[:error] %>
+
+ <% end %>
+
+ <% if @paperless_status[:configured] %>
+
+ Signed documents are automatically uploaded to Paperless-ngx when all parties complete signing.
+
+ <% end %>
+
diff --git a/app/views/notifications_settings/index.html.erb b/app/views/notifications_settings/index.html.erb
index db4bec98..3cc76174 100644
--- a/app/views/notifications_settings/index.html.erb
+++ b/app/views/notifications_settings/index.html.erb
@@ -21,6 +21,7 @@
<% end %>
<%= render 'bcc_form', config: @bcc_config %>
+ <%= render 'paperless_status' %>
<%= t('sign_request_email_reminders') %>
diff --git a/config/initializers/paperless_ngx.rb b/config/initializers/paperless_ngx.rb
new file mode 100644
index 00000000..858a0b9c
--- /dev/null
+++ b/config/initializers/paperless_ngx.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+Rails.application.config.after_initialize do
+ status = Submissions::UploadToPaperless.health_check
+
+ if !status[:configured]
+ Rails.logger.info('[Paperless-ngx] Integration not configured (PAPERLESS_NGX_URL / PAPERLESS_NGX_TOKEN not set)')
+ elsif status[:reachable]
+ Rails.logger.info("[Paperless-ngx] Connected to #{status[:url]}")
+ else
+ Rails.logger.warn("[Paperless-ngx] Configured but unreachable at #{status[:url]}: #{status[:error]}")
+ end
+rescue StandardError => e
+ Rails.logger.warn("[Paperless-ngx] Health check failed during startup: #{e.message}")
+end
diff --git a/lib/submissions/upload_to_paperless.rb b/lib/submissions/upload_to_paperless.rb
index 6cc0ed83..d8fcbb6b 100644
--- a/lib/submissions/upload_to_paperless.rb
+++ b/lib/submissions/upload_to_paperless.rb
@@ -26,6 +26,25 @@ module Submissions
ENV['PAPERLESS_NGX_URL'].present? && ENV['PAPERLESS_NGX_TOKEN'].present?
end
+ def health_check
+ return { configured: false, reachable: false, url: nil, error: nil } unless configured?
+
+ url = ENV['PAPERLESS_NGX_URL'] # rubocop:disable Style/FetchEnvVar
+ response = connection.get('/api/') do |req|
+ req.headers['Authorization'] = "Token #{ENV['PAPERLESS_NGX_TOKEN']}" # rubocop:disable Style/FetchEnvVar
+ req.options.timeout = 3
+ req.options.open_timeout = 3
+ end
+
+ if response.status < 400
+ { configured: true, reachable: true, url: url, error: nil }
+ else
+ { configured: true, reachable: false, url: url, error: "HTTP #{response.status}" }
+ end
+ rescue Faraday::Error => e
+ { configured: true, reachable: false, url: url, error: e.message }
+ end
+
def documents_to_upload(submission, title)
documents = []
diff --git a/spec/lib/submissions/upload_to_paperless_spec.rb b/spec/lib/submissions/upload_to_paperless_spec.rb
index 1dcd49b4..8ae29803 100644
--- a/spec/lib/submissions/upload_to_paperless_spec.rb
+++ b/spec/lib/submissions/upload_to_paperless_spec.rb
@@ -205,4 +205,75 @@ RSpec.describe Submissions::UploadToPaperless do
end
end
end
+
+ describe '.health_check' do
+ context 'when not configured' do
+ before do
+ allow(ENV).to receive(:[]).with('PAPERLESS_NGX_URL').and_return(nil)
+ allow(ENV).to receive(:[]).with('PAPERLESS_NGX_TOKEN').and_return(nil)
+ end
+
+ it 'returns configured false with no error' do
+ result = described_class.health_check
+
+ expect(result).to eq(configured: false, reachable: false, url: nil, error: nil)
+ end
+ end
+
+ context 'when configured and reachable' do
+ before do
+ stub_request(:get, "#{paperless_url}/api/")
+ .with(headers: { 'Authorization' => "Token #{paperless_token}" })
+ .to_return(status: 200, body: '{"version": "2.0"}')
+ end
+
+ it 'returns configured and reachable with the URL' do
+ result = described_class.health_check
+
+ expect(result).to eq(configured: true, reachable: true, url: paperless_url, error: nil)
+ end
+ end
+
+ context 'when configured but server returns error' do
+ before do
+ stub_request(:get, "#{paperless_url}/api/")
+ .to_return(status: 500, body: 'Internal Server Error')
+ end
+
+ it 'returns configured but unreachable with HTTP status error' do
+ result = described_class.health_check
+
+ expect(result).to eq(configured: true, reachable: false, url: paperless_url, error: 'HTTP 500')
+ end
+ end
+
+ context 'when configured but connection times out' do
+ before do
+ stub_request(:get, "#{paperless_url}/api/")
+ .to_timeout
+ end
+
+ it 'returns configured but unreachable with timeout error' do
+ result = described_class.health_check
+
+ expect(result[:configured]).to be true
+ expect(result[:reachable]).to be false
+ expect(result[:url]).to eq(paperless_url)
+ expect(result[:error]).to be_present
+ end
+ end
+
+ context 'when configured but connection refused' do
+ before do
+ stub_request(:get, "#{paperless_url}/api/")
+ .to_raise(Faraday::ConnectionFailed.new('Connection refused'))
+ end
+
+ it 'returns configured but unreachable with connection error' do
+ result = described_class.health_check
+
+ expect(result).to eq(configured: true, reachable: false, url: paperless_url, error: 'Connection refused')
+ end
+ end
+ end
end
diff --git a/spec/system/notifications_settings_spec.rb b/spec/system/notifications_settings_spec.rb
index 2edd3eb5..d2f33d73 100644
--- a/spec/system/notifications_settings_spec.rb
+++ b/spec/system/notifications_settings_spec.rb
@@ -69,6 +69,59 @@ RSpec.describe 'Notifications Settings' do
end
end
+ context 'when paperless-ngx is not configured' do
+ before do
+ allow(ENV).to receive(:[]).and_call_original
+ allow(ENV).to receive(:[]).with('PAPERLESS_NGX_URL').and_return(nil)
+ allow(ENV).to receive(:[]).with('PAPERLESS_NGX_TOKEN').and_return(nil)
+ end
+
+ it 'shows not configured status' do
+ visit settings_notifications_path
+
+ expect(page).to have_content('Paperless-ngx')
+ expect(page).to have_content('Not Configured')
+ end
+ end
+
+ context 'when paperless-ngx is configured and reachable' do
+ before do
+ allow(ENV).to receive(:[]).and_call_original
+ allow(ENV).to receive(:[]).with('PAPERLESS_NGX_URL').and_return('http://paperless:8000')
+ allow(ENV).to receive(:[]).with('PAPERLESS_NGX_TOKEN').and_return('test-token')
+
+ stub_request(:get, 'http://paperless:8000/api/')
+ .to_return(status: 200, body: '{}')
+ end
+
+ it 'shows connected status with URL' do
+ visit settings_notifications_path
+
+ expect(page).to have_content('Paperless-ngx')
+ expect(page).to have_content('Connected')
+ expect(page).to have_content('http://paperless:8000')
+ end
+ end
+
+ context 'when paperless-ngx is configured but unreachable' do
+ before do
+ allow(ENV).to receive(:[]).and_call_original
+ allow(ENV).to receive(:[]).with('PAPERLESS_NGX_URL').and_return('http://paperless:8000')
+ allow(ENV).to receive(:[]).with('PAPERLESS_NGX_TOKEN').and_return('test-token')
+
+ stub_request(:get, 'http://paperless:8000/api/')
+ .to_return(status: 500, body: 'Internal Server Error')
+ end
+
+ it 'shows unreachable status with error' do
+ visit settings_notifications_path
+
+ expect(page).to have_content('Paperless-ngx')
+ expect(page).to have_content('Unreachable')
+ expect(page).to have_content('HTTP 500')
+ end
+ end
+
context 'when changes sign request email reminders settings' do
it 'updates first reminder duration' do
visit settings_notifications_path