{% extends "base.html" %} {% block title %}Onboard clients & jobs — BackupWatch{% endblock %} {% block main_class %}onboarding-workspace{% endblock %} {% block content %}

Onboard clients & jobs

Import inventory, verify report routing, then activate selected jobs. Drafts and observing jobs stay visible here.

{% if msg %}{% endif %}
{% for label in ['draft','observing','monitoring','retired'] %}{{ label|capitalize }} · {{ counts.get(label,0) }}{% endfor %}
Connection & handover checks

Mailbox: {{ 'a successful poll has been recorded' if mailbox_ok else 'awaiting first successful poll' }}. Mailbox settings

Schedule timezone: {{ timezone }}. Alerts go to {{ recipients or 'no destination configured' }}.

Notification delivery: {{ notification_test or 'not confirmed during onboarding' }}. Send a test in Settings

A received failure can verify routing. Monitoring still requires a successful report within the expected window.

Import clients & jobs

Download CSV template. Use a stable client code and job reference. Reimports preserve blank settings; updates require explicit selection.

Up to 1000 job rows / 900 KB per batch. You can map column names on the next screen.
Discover jobs from reports

Group up to 500 reports from a date range into proposed jobs. Discovery does not send alerts, move messages or change their read state. An inventory is still needed to find jobs that never report.

Maximum 90 days. Mailbox discovery uses provider date boundaries (Microsoft 365: UTC); stored reports use {{ timezone }}. Narrow the range if the sample limit is reached.

Suggest jobs across all clients (AI-assisted)

Groups every report waiting in the Review queue, for every client, using the same heuristic grouping as "Discover jobs from reports" above — nothing is filed, matched or marked read. When AI assistance is enabled in Settings, each client with waiting mail also gets its own Claude refinement pass, sent one client at a time, shown for review before anything sends.

Find jobs for a client

Search the Review queue for mail this client's email identifiers claim, grouped into proposed jobs. Nothing is filed, created or marked read until you save a job in the wizard.

{% if find %} {% if find.error %} {% else %}

Searched {{ find.searched }} waiting report(s) in that range{% if find.capped %} — sample limit reached; narrow the range{% endif %}. {{ find.groups|length }} group(s) match {{ find.client.display_name }}{% if find.ai_applied %} — refined by Claude{% endif %}.

{% if find.ai_snapshot and not find.ai_applied %} {# the button never sends: it opens a dialog showing the stored snapshot first #}
Optional. Sends subject lines and sender addresses only.

Send to Anthropic?

{{ find.ai_sent }}{% if find.ai_total > find.ai_sent %} of {{ find.ai_total }}{% endif %} subject line(s) and sender address(es) for {{ find.client.display_name }} will be sent to api.anthropic.com using your API key. {% if find.ai_total > find.ai_sent %}The {{ find.ai_total - find.ai_sent }} older email(s) are not included and keep their local grouping.{% endif %}

No message bodies, recipients or attachments are included. The request also carries BackupWatch's own instructions and your model settings.

Show exactly what will be sent
{% endif %}
{% for g in find.groups %} {# the emails themselves, on their own full-width row: nobody should create a job from a count alone, and an address needs room to be read #} {% else %}{% endfor %}
Proposed jobReportsRules it would use
{{ g.name }}{% if g.refined %} AI{% endif %} {{ g.count }} {% if g.sender_rule %}from contains {{ g.sender_rule }}
{% endif %}{% if g.subject_rule %}subject contains {{ g.subject_rule }}{% endif %}
Vendor: {{ vendor_labels.get(g.vendor, g.vendor) }}
{% if g.existing_job %}Already covered by {{ g.existing_job }}{% else %}Create job in wizard{% endif %}
Show the {{ g.samples|length }} email(s) behind “{{ g.name }}” {% for s in g.samples %}{% endfor %}
ReceivedFromSubject
{{ s.received_at|dt if s.received_at else '—' }} {{ s.from }} {{ s.subject }}
{% if g.extra %}

…and {{ g.extra }} more not shown.

{% endif %}
No waiting mail matches this client's identifiers in that range. Check the identifiers on the client page, or widen the dates.
{% if find.uncovered %}

{{ find.uncovered|length }} group(s) below were not sent to Claude.

{% for g in find.uncovered %}{% endfor %}
Proposed jobReportsRules it would use
{{ g.name }} {{ g.count }} {% if g.sender_rule %}from contains {{ g.sender_rule }}
{% endif %}{% if g.subject_rule %}subject contains {{ g.subject_rule }}{% endif %}
Vendor: {{ vendor_labels.get(g.vendor, g.vendor) }}
{% if g.existing_job %}Already covered by {{ g.existing_job }}{% else %}Create job in wizard{% endif %}
Show the {{ g.samples|length }} email(s) behind “{{ g.name }}” {% for s in g.samples %}{% endfor %}
ReceivedFromSubject
{{ s.received_at|dt if s.received_at else '—' }} {{ s.from }} {{ s.subject }}
{% if g.extra %}

…and {{ g.extra }} more not shown.

{% endif %}
{% endif %} {% endif %} {% endif %}

Job inventory

{% for job in jobs %}{% else %}{% endfor %}
Client / jobMonitoringEvidenceSchedule ({{ timezone }})Owner / review
{{ job.client.display_name }}
{{ job.name }}
{{ job.external_ref or 'No inventory reference' }}
{{ job.onboarding_state|capitalize }}{% if not job.active %}
Not receiving matches{% endif %}{% if job.silenced %}
Silenced{% endif %}{% if job.muted_until %}
Muted until {{ job.muted_until|dt }}{% endif %}
{% if job.id in stats %}Last report: {{ stats[job.id][0]|dt }}
Last success: {{ stats[job.id][1]|dt if stats[job.id][1] else 'None' }}{% else %}Awaiting sample{% endif %}{% if not job.rules %}
Add matching rules{% endif %}
{{ 'Daily by ' ~ job.expected_time ~ ' + ' ~ job.grace_hours ~ 'h grace' if job.frequency == 'daily' else 'Every ' ~ job.interval_minutes ~ ' min; ' ~ job.window_start ~ '–' ~ job.window_end }}
Days: {{ job.days or 'every day' }} (0=Mon)
Next: {{ deadlines[job.id]|dt if deadlines[job.id] else 'Check schedule' }}
{{ job.setup_owner or 'Unassigned' }}
{{ job.review_on or 'Review date needed' }}{% if job.review_on and job.review_on < today and job.onboarding_state != 'monitoring' %} · Review overdue{% endif %}
No jobs in this view. Import an inventory, discover reports, or change the filter.
0 selected on this page
Change settings for selected jobs

Tick only the fields to change. Other settings and individual overrides stay as they are. Review old and new values before applying.

{% for key,label,type,value in [('expected_time','Expected by','time','06:00'),('grace_hours','Grace hours','number','4'),('window_start','Window start','time','00:00'),('window_end','Window end','time','23:59'),('grace_minutes','Grace minutes','number','30'),('setup_owner','Setup owner','text',user.email),('review_on','Review date','date',due)] %}
{% endfor %}
{% for day in ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'] %}{% endfor %}
Retire selected jobs

Stop matching and monitoring while retaining job identity, reports and audit history. Retirement requires confirmation in the preview.

{% if page > 1 %}Previous page{% endif %} Page {{ page }} · up to 100 jobs per page {% if more %}Next page{% endif %}

Saved batches

Resume previews and correct failed rows. A saved row is never applied twice.

{% for batch in batches %}{% else %}{% endfor %}
CreatedBatchTechnician
{{ batch.created_at|dt }}{{ batch.kind|capitalize }} · {{ batch.id[:8] }}{{ batch.created_by }}
No batches yet.
{% endblock %}