Ticket-driven appointment scheduling with external scheduling providers (Calnode first)

Ticket-driven appointment scheduling with external scheduling providers (Calnode first)

1. What is your original issue/pain point you want to solve?

Support tickets frequently reach a point where the next action must occur during a scheduled remote session, telephone call, consultation, or on-site visit.

The agent normally determines several requirements before asking the customer to select a time:

  • Which customer or ticket contact should schedule the appointment
  • What type of appointment is required
  • How much time is needed
  • Which agent, technician, or team should perform it
  • Whether it is remote, telephone, or on-site
  • Whether the customer may choose from all future availability or only a restricted date window

Zammad currently manages the ticket conversation, customer identity, assigned agent, ticket state, and service history. An external scheduling service can calculate availability and create the calendar appointment. However, there is no first-class workflow connecting these systems.

As a result, agents must manually:

  1. Find or construct the correct booking link.
  2. Send it to the customer.
  3. Ask the customer to enter the ticket number or other correlation information.
  4. Monitor whether an appointment was booked.
  5. Copy the selected time, technician, meeting link, and booking status back into Zammad.
  6. Update the ticket state and pending time.
  7. Repeat the process if the invitation expires, is cancelled, or must be replaced.

This creates several problems:

  • The scheduling request may not appear consistently in the ticket history.
  • Customers may select the wrong service, duration, or technician.
  • Ticket numbers entered by customers are not reliable authorization or correlation.
  • Agents cannot easily see whether the ticket is awaiting scheduling or already scheduled.
  • Appointment changes made in the external scheduler may not be reflected in Zammad.
  • Calendar or meeting information must be copied manually.
  • Automation implemented independently by each operator becomes fragile and difficult to maintain.

The requested feature is a native Zammad workflow for creating and tracking a ticket-related scheduling invitation through an external scheduling provider.

Zammad would remain the system of record for the support conversation and ticket workflow. The scheduling provider would remain responsible for availability calculations, calendar integration, online-meeting creation, reminders, and booking lifecycle.

2. Which are one or two concrete situations where this problem hurts the most?

Situation 1: Remote technical-support appointment

An agent determines that a ticket requires a 90-minute remote session with a particular technician.

The desired workflow is:

  1. The agent opens the ticket and selects Schedule appointment.
  2. The agent selects:
    • The ticket contact who should receive the invitation
    • Remote Support
    • A duration of 90 minutes
    • A specific technician or eligible technician pool
    • An optional scheduling window
  3. Zammad requests a one-time invitation from the scheduling provider.
  4. Zammad sends the scheduling URL as a public email article so the initial invitation remains in the ticket history.
  5. The customer selects an available time.
  6. The scheduling provider creates the calendar appointment and online meeting.
  7. The provider notifies Zammad through a signed webhook.
  8. Zammad records the appointment, changes the scheduling status to Scheduled, and optionally sets the ticket’s pending_time.
  9. Zammad adds an internal article recording the booking details.

The customer should not need to choose the service, duration, or technician again, and should not need to enter a ticket number manually.

Situation 2: Restricted on-site appointment

An agent determines that an on-site visit requires three hours and must be performed by a specific technician between two permitted dates.

For example:

Schedule a three-hour on-site visit with Kat between October 5 and October 9.

The scheduling provider must calculate slots using the complete three-hour duration, calendar conflicts, travel or appointment buffers, technician availability, and the permitted date window.

Zammad should initiate and track this request, but it should not attempt to calculate calendar availability itself.

This workflow also generalizes beyond IT support:

  • Field-service visits
  • Customer-success onboarding
  • Sales consultations
  • Medical-office follow-ups
  • Legal or financial consultations
  • Property-management appointments
  • Equipment installation
  • Training sessions
  • Escalations requiring multiple participants

3. Why is it not solvable with the Zammad standard?

Zammad already provides useful integration components:

  • Custom ticket attributes
  • Triggers and schedulers
  • Outgoing webhooks
  • A REST API
  • Ticket articles
  • Ticket states and pending times

These components can support a custom bridge, but they do not provide the complete agent workflow or a durable appointment relationship.

Custom fields can store a booking ID or date, but they do not provide:

  • A Schedule appointment action
  • Contact selection
  • Appointment-type selection
  • Duration and scheduling-window controls
  • Agent-to-calendar-host mapping
  • Creation of a one-time scheduling invitation
  • A structured relationship between a ticket and one or more appointments
  • Appointment lifecycle history
  • Secure provider callback handling
  • Idempotent processing of duplicate callbacks
  • Consistent cancellation or replacement behavior
  • A reusable provider integration interface

A normal public booking page is also insufficient. It requires the customer to make decisions already made by the agent and generally cannot securely encode:

  • The authoritative ticket reference
  • The intended recipient
  • The required duration
  • The permitted technician or team
  • A ticket-specific date window
  • Whether the link has already been used or cancelled

This could be assembled as an installation-specific collection of custom fields, triggers, webhook receivers, macros, and external automation. However, every Zammad operator needing appointment scheduling would have to recreate the same workflow, UI, security checks, synchronization, and failure handling.

4. What is your expectation/what do you want to achieve?

I propose a first-class ticket appointment or scheduling integration capability, initially implemented with Calnode but designed so other scheduling providers could be added later.

The feature should not turn Zammad into a scheduling engine. Its responsibilities would be:

  • Let an authorized agent initiate scheduling from a ticket.
  • Send the initial invitation through Zammad.
  • Correlate the external invitation and booking with the ticket.
  • Display current scheduling information to agents.
  • Receive authenticated lifecycle updates.
  • Update the ticket workflow in response.
  • Preserve a clear audit history.

The external provider would remain responsible for:

  • Calendar availability
  • Time-zone and daylight-saving calculations
  • Appointment duration and buffers
  • Host routing
  • Calendar event creation
  • Microsoft Teams or other meeting creation
  • Confirmations and reminders
  • Customer rescheduling and cancellation pages

Proposed agent workflow

A Schedule appointment action could be available from the ticket action menu or an appointment sidebar.

The form could contain:

  • Contact
    • Default to the ticket customer
    • Allow selection of another eligible ticket contact or email recipient
  • Appointment type
    • Loaded from the configured scheduling provider
  • Duration
    • Defaulted from the appointment type
    • Editable only when the provider reports an allowed range
  • Host selection
    • Ticket owner
    • A particular Zammad agent mapped to a provider host
    • Any eligible host
    • A configured team or rotation pool
  • Location
    • Inherited from the appointment type when possible
  • Earliest date
    • Optional
  • Latest date
    • Optional
  • Customer time zone
    • Defaulted from the customer or browser when available
  • Invitation expiration
    • Defaulted from integration settings
  • Message template
    • A configurable public email message containing the scheduling link

After confirmation, Zammad would:

  1. Create the invitation through the provider.
  2. Store the provider’s invitation identifier and correlation data.
  3. Create a public email article containing the scheduling URL.
  4. Set the appointment status to Awaiting Customer.
  5. Optionally change the ticket state according to administrator-configured rules.
  6. Record an internal audit article.

Proposed ticket display

A ticket appointment panel could show:

  • Appointment status
  • Appointment type
  • Recipient
  • Duration
  • Assigned or eligible host
  • Permitted scheduling window
  • Scheduled start and end
  • Time zone
  • Location
  • Online meeting URL
  • External invitation ID
  • External booking ID
  • Date invitation was sent
  • Invitation expiration
  • Last provider update
  • Actions such as cancel, replace invitation, or open in provider

Example:

Appointment status: Scheduled
Type: Remote Support
Contact: Jane Customer
Technician: Kat
Start: October 7, 2026 at 10:00 AM America/Los_Angeles
Duration: 90 minutes
Location: Microsoft Teams
Invitation: si_01J...
Booking: bk_01J...

Meeting URLs and other customer-visible information should only be displayed to users with permission to view the ticket.

Proposed data model

A ticket may eventually require more than one appointment, so this would be better represented as a related resource than as a single set of ticket attributes.

A possible appointment record could contain:

{
  "id": 123,
  "ticket_id": 456,
  "provider": "calnode",
  "provider_invitation_id": "si_01J...",
  "provider_booking_id": "bk_01J...",
  "status": "scheduled",
  "event_type_id": "onsite-support",
  "event_type_name": "On-site Support",
  "recipient_user_id": 789,
  "recipient_name": "Jane Customer",
  "recipient_email": "jane@example.com",
  "duration_minutes": 180,
  "host_mappings": [
    {
      "zammad_user_id": 42,
      "provider_host_id": "host_01J...",
      "role": "required"
    }
  ],
  "available_from": "2026-10-05",
  "available_until": "2026-10-09",
  "availability_timezone": "America/Los_Angeles",
  "expires_at": "2026-10-10T07:00:00Z",
  "scheduled_start_at": "2026-10-07T17:00:00Z",
  "scheduled_end_at": "2026-10-07T20:00:00Z",
  "location_type": "onsite",
  "location_value": "123 Main Street",
  "meeting_url": null,
  "created_by_id": 42,
  "created_at": "2026-10-01T18:00:00Z",
  "updated_at": "2026-10-02T16:30:00Z"
}

The exact schema is open for discussion. The important requirement is to preserve a structured, auditable relationship between the ticket, invitation, and final booking.

Appointment lifecycle

A possible Zammad-side lifecycle is:

Creating
Awaiting Customer
Scheduled
Cancelled
Expired
Failed
Completed

The scheduling lifecycle should be separate from the ticket state. Administrators could optionally map scheduling events to ticket actions.

For example:

Scheduling event Optional Zammad action
Invitation created Change ticket to Scheduling Requested
Invitation sent Set scheduling status to Awaiting Customer
Appointment booked Change ticket to Scheduled
Appointment booked Set pending_time before the appointment
Appointment cancelled Reopen ticket or return it to the assigned agent
Invitation expired Reopen ticket and notify the owner
Appointment rescheduled Update pending_time and add an internal article
Appointment completed Return ticket to an active state

These mappings should be configurable rather than hard-coded.

Customer communication

The initial scheduling request should be sent by Zammad as a normal public email article.

This is important because:

  • The request remains part of the ticket history.
  • Replies return to the ticket.
  • Existing email channels, signatures, branding, and delivery configuration are reused.
  • Agents can see exactly what the customer received.
  • The scheduling provider does not need to impersonate the helpdesk.
  • Other channels could potentially be supported later.

A configurable message could resemble:

Hello #{ticket.customer.firstname},

We need to schedule a remote support appointment for this request.

Please use the secure link below to select an available time:

#{appointment.scheduling_url}

This invitation is intended for you and will expire on
#{appointment.expires_at}.

After booking, the scheduling provider may continue handling its normal calendar invitation, confirmation, reminder, cancellation, and rescheduling messages.

Zammad should add internal articles for important lifecycle changes without duplicating customer-facing messages unnecessarily.

Provider configuration

An administrator could configure one or more scheduling providers with:

  • Provider type
  • Base URL
  • API credential
  • Incoming webhook secret or public-key configuration
  • Default invitation expiration
  • Default customer message template
  • Ticket-state mappings
  • Appointment-status mappings
  • Group restrictions
  • Agent-to-provider-host mappings
  • Optional appointment-type restrictions by Zammad group

Secrets should be stored using the same protections as other Zammad integration credentials and must never be included in logs or webhook payloads.

Generic provider interface

Although Calnode would be the initial provider, I suggest separating the Zammad workflow from provider-specific API code.

A provider adapter would conceptually support operations such as:

list_event_types
list_hosts
create_invitation
get_invitation
cancel_invitation
replace_invitation
get_booking
verify_webhook
normalize_webhook_event

Not every operation must be implemented in the first release. The interface could grow incrementally.

The minimum provider response when creating an invitation would be:

{
  "invitation_id": "si_01J...",
  "status": "active",
  "scheduling_url": "https://schedule.example.com/s/opaque-token",
  "expires_at": "2026-10-10T07:00:00Z"
}

Zammad would send a generic external reference so the provider can include it in later callbacks:

{
  "external": {
    "system": "zammad",
    "reference": "ticket:456",
    "url": "https://support.example.com/#ticket/zoom/456"
  }
}

The reference should use Zammad’s internal ticket ID or another non-ambiguous identifier, rather than relying only on the human-facing ticket number.

Incoming scheduling events

Zammad would expose an authenticated integration endpoint for events such as:

scheduling_invitation.created
scheduling_invitation.cancelled
scheduling_invitation.expired
booking.created
booking.rescheduled
booking.cancelled

A booking event might resemble:

{
  "id": "evt_01J...",
  "event": "booking.created",
  "occurred_at": "2026-10-02T16:30:00Z",
  "data": {
    "invitation_id": "si_01J...",
    "booking_id": "bk_01J...",
    "duration_minutes": 180,
    "start_at": "2026-10-07T17:00:00Z",
    "end_at": "2026-10-07T20:00:00Z",
    "timezone": "America/Los_Angeles",
    "host": {
      "id": "host_01J...",
      "name": "Kat"
    },
    "location": {
      "type": "onsite",
      "value": "123 Main Street"
    },
    "external": {
      "system": "zammad",
      "reference": "ticket:456",
      "url": "https://support.example.com/#ticket/zoom/456"
    }
  }
}

Processing should be idempotent. Receiving the same event more than once must not create duplicate articles or repeat ticket-state transitions.

Unknown, invalid, expired, or incorrectly signed events should be rejected and logged without modifying a ticket.

Permissions and security

Suggested permissions could include:

  • View ticket appointments
  • Create scheduling invitations
  • Cancel or replace invitations
  • Cancel or reschedule bookings
  • View meeting and location information
  • Administer scheduling providers
  • Administer host and event-type mappings

Additional security requirements:

  • Scheduling API calls occur server-to-server.
  • Provider credentials are never exposed to the browser.
  • Incoming callbacks require signature verification.
  • Callback events are idempotent.
  • Scheduling URLs are treated as bearer credentials.
  • Raw scheduling tokens are not written to normal application logs.
  • Agents may only schedule tickets they are permitted to access.
  • Agents may only select hosts and appointment types permitted for their group.
  • The recipient should default to a contact already associated with the ticket.
  • Every create, cancel, replace, and update operation should be auditable.
  • Customer-visible location and meeting information should follow normal ticket permissions.

Failure handling

The UI should distinguish between:

  • Provider unavailable
  • Invitation creation rejected
  • Invitation created but email article failed
  • Invitation expired
  • Webhook signature rejected
  • Unknown ticket correlation
  • Agent-to-host mapping missing
  • Appointment type no longer available
  • Duplicate webhook event
  • Provider booking deleted or cancelled externally

If the provider creates an invitation but Zammad cannot send the article, the agent should be able to retry delivery without creating a duplicate invitation.

If the outcome of an API request is uncertain because of a timeout, Zammad should use an idempotency key or query the provider before retrying creation.

Calnode coordination

Calnode is a self-hostable scheduling platform.

I proposed a first-class scheduling-invitation resource to Calnode for this workflow:

The Calnode maintainer accepted the proposal as fitting Calnode’s scope and agreed with the division of responsibility:

  • The event type provides reusable defaults.
  • A scheduling invitation snapshots authorized request-specific overrides.
  • The final booking is the immutable result.
  • Invitation-specific duration, host, and date-window restrictions must be enforced by Calnode’s slot and booking engine.
  • External references such as {system, reference, url} are appropriate.
  • Both provider-delivered and externally delivered invitation modes are useful.
  • External delivery should be supported so Zammad can send the first message.
  • Invitation and booking webhooks should carry the external reference back to Zammad.

Calnode is tracking an incremental implementation consisting of:

  1. Invitation records with opaque, expiring, single-use tokens hashed at rest
  2. Duration ranges on event types and per-invitation duration overrides
  3. Fixed-host restrictions
  4. Server-enforced availability windows
  5. Invitation lifecycle and enriched booking webhooks

No implementation timeline has been committed, but the Calnode maintainer has accepted the architecture and invited testing with Microsoft 365, Microsoft Teams, and Zammad.

This allows the two projects to maintain a clean separation:

Calnode

  • Availability and slot calculation
  • Calendar connections
  • Duration and buffer enforcement
  • Host selection and routing
  • Date-window enforcement
  • Calendar event creation
  • Online meeting creation
  • Booking confirmations and reminders
  • Customer booking, rescheduling, and cancellation pages
  • Signed lifecycle webhooks

Zammad

  • Agent scheduling workflow
  • Ticket contact selection
  • Ticket and appointment correlation
  • Initial customer email
  • Appointment status display
  • Ticket-state and pending-time updates
  • Internal audit articles
  • Provider configuration and host mapping
  • Secure webhook reception

Proposed incremental implementation

Phase 1: Minimal usable workflow

  • One configured Calnode provider
  • Agent-to-Calnode-host mapping
  • Appointment-type selection
  • One ticket contact
  • Fixed technician
  • Authorized duration override
  • Optional earliest/latest dates
  • Create invitation from a ticket
  • Send link through a public Zammad email article
  • Store invitation ID and status
  • Receive and verify booking.created
  • Display scheduled time, host, and location
  • Add an internal booking article
  • Configurable ticket-state and pending_time update

This would support the core remote and on-site support workflows.

Phase 2: Lifecycle management

  • Invitation expiration
  • Invitation cancellation
  • Replacement invitations
  • Booking cancellation
  • Booking rescheduling
  • Retry and reconciliation tools
  • Provider event-delivery history
  • Agent notifications for failures or expiration

Phase 3: Teams and routing

  • Any eligible technician
  • Rotation pools
  • Multiple required participants
  • Group-specific appointment-type permissions
  • Multiple contacts
  • Multiple appointments per ticket
  • More detailed lifecycle automation

Phase 4: Additional providers

Once the Zammad-side provider boundary is stable, adapters could be added for other self-hosted or hosted schedulers where their APIs support the required invitation semantics.

Non-goals

The proposal does not require Zammad to:

  • Calculate calendar availability
  • Connect directly to every agent calendar
  • Implement time-zone slot calculations
  • Create Microsoft Teams meetings
  • Send appointment reminders
  • Become a general Calendly replacement
  • Trust appointment parameters supplied by the customer
  • Store provider OAuth tokens for individual calendars

Those responsibilities remain with the scheduling provider.

Questions for the Zammad maintainers and community

  1. Does ticket-driven appointment scheduling fit Zammad’s intended scope?
  2. Would a related ticket-appointment resource be preferable to adding scheduling attributes directly to tickets?
  3. Would the project prefer:
    • A Calnode-specific integration first, with later generalization, or
    • A provider interface introduced from the beginning?
  4. Where would the most appropriate agent UI live: a ticket sidebar, ticket action, or both?
  5. Is there an existing integration pattern that should be reused for secure incoming provider callbacks?
  6. Should ticket-state and pending_time changes be part of the integration, or delegated to normal Zammad triggers operating on appointment status?
  7. Would the maintainers be open to an incremental contribution beginning with the Phase 1 workflow above?
  8. Are there architectural concerns with maintaining a one-to-many relationship between tickets and external appointments?
  9. Should customer email delivery always remain in Zammad, or should the provider-delivery mode also be exposed?
  10. Which parts should be implemented in Zammad core versus a separately maintained package or integration service?

I am willing to help refine the design, contribute implementation work, coordinate the Calnode side, and test the complete workflow with self-hosted Zammad, Microsoft 365 calendars, Microsoft Teams meeting creation, and Calnode.

Relevant documentation

Zammad environment

  • Deployment status: self-hosted Zammad deployment
  • Zammad version: Latest supported self-hosted release at deployment
  • Installation type: Self-hosted
  • Average concurrent agent count: 1-3
  • Average tickets per day: 1-10
  • Roles involved: Helpdesk agents, field technicians, administrators, customers, and other ticket contacts