Skip to main content

Client Invitations

Audience: Sales / Agency-success · Where in app: /agency/client-invitations (list, create) · Plan availability: Agency tier (verify specifics)

Client invitations are white-label marketing/outreach emails an agency sends to prospective clients to pitch Knock Knock under the agency's own brand. Each invitation is a small campaign (up to 20 recipients) with a subject, a personalized note, an optional demo video, a PDF brochure, a price, and a booking/CTA link. The emails go out branded as the agency (logo, primary color, reply-to). This is not the staff-invitation flow (Staff Management) and it does not create accounts.

Naming vs. behavior

Despite the name, this is a prospecting email tool, not a magic-link account-onboarding flow. Sending an invitation queues branded emails; it does not provision a client company or a login. To actually create a client account, use Managing Companies.

What it does

  • Compose a branded pitch email: subject (with suggestions), personal note (supports a [First Name] token), demo video URL, PDF attachment, booking link, price, tagline.
  • Add recipients by pasting/typing emails or importing a CSV — max 20 per campaign.
  • Preview the rendered email before sending.
  • Send — queues one white-label email per recipient via the platform mail queue.
  • List past campaigns with recipient counts and per-recipient status.

How it works

On create (POST /client-invitations, store), the controller:

  1. Verifies the caller is an agency user and resolves the agency.
  2. Enforces 1–20 recipients.
  3. Derives a first name per recipient from the email local-part (sarah.j@… → "Sarah") for the [First Name] token.
  4. Creates a client_invitations record (status: 'sending').
  5. Builds template data from agency branding (logo, theme_colors.primary, reply_email or owner email) and the invitation content. Demo video falls back to an admin default video for the client_invitation category if none is provided.
  6. Queues a white-label email per recipient (emailQueue.add, template agency/client-invitation.html, white_label: true, sender_name = agency.name, reply_to = agency reply email).
  7. Flips the record to status: 'sent'.

Preview (POST /client-invitations/preview) renders the same template with a mock invitation and returns HTML — nothing is sent or stored.

Source: frontend/src/views/agency/client-invitations/{Create,List}.vue, frontend/src/repositories/agency/clientInvitations.js, backend/app/controllers/ClientInvitationController.js.

Configuration & options

Compose form (client-invitations/Create.vue)

FieldDefaultNotes
subjecta suggested lineThree quick-pick suggestions provided
personal_notea starter note[First Name] is replaced per recipient; newlines → <br>
demo_video_url''Falls back to admin default client_invitation video if empty
pdf_attachment_filenullUploaded via streamUpload('/files', …), max 10MB; stores file id
booking_link''CTA target; falls back to mailto:<reply email> if empty
price$97/moFree-text string shown in the email
taglineYour Digital Growth PartnerShown under the agency name
RecipientsPasted text or CSV; split on commas/newlines; only valid-looking emails kept; max 20

Endpoints (clientInvitations.js)

ActionRepository fnEndpoint
List campaignsgetClientInvitations(params)GET /client-invitations (paginated, populates PDF)
Get onegetClientInvitation(id)GET /client-invitations/:id
Create + sendcreateClientInvitation(data)POST /client-invitations
Preview HTMLpreviewClientInvitation(data)POST /client-invitations/preview

ClientInvitationModel (collection client_invitations)

FieldDefaultNotes
agency_id, user_idOwning agency + sender
subject, personal_noterequiredCampaign content
demo_video_urlnullOptional
pdf_attachment_filenullfiles ref
booking_linknullCTA
price$97/mo
taglineYour Digital Growth Partner
recipients[][]Each: email, first_name, status (queued/sent/failed)
total_recipients0Count
statussendingCampaign status: draft / sending / sent / partial_failure

Lifecycle & states

  • Campaign status: draftsendingsent, or partial_failure if some recipients fail. The current store flow creates as sending and flips to sent once all emails are queued.
  • Recipient status: queuedsent → (failed on delivery error). Recipients start queued at creation.
  • Campaigns are immutable after send in the UI (list + preview only; no edit/resend button in this cluster — verify if resend is expected).

Behaviors & edge cases

  • Hard cap of 20 recipients. Both UI and backend enforce it; >20 returns "Maximum 20 recipients per campaign".
  • Invalid emails are dropped client-side before submit (regex filter); a campaign with zero valid recipients is rejected ("At least one recipient is required").
  • Branding comes from the agency record — logo (logo_file), theme_colors.primary, and reply_email (falling back to the owner's email). White-label send uses agency.name as the sender name.
  • Default demo video fallback. If no demo_video_url, the platform tries the admin default video for the client_invitation category (default-flagged first, else newest active).
  • No account is created. This tool only emails; client provisioning is separate.
  • Permission: the controller checks isAgencyUser (agency owner or staff); there is no dedicated per-permission gate for this tool in the controller (verify whether staff should be restricted).

Plan & limits

  • 20 recipients per campaign (code-enforced). No documented per-agency campaign cap (verify).
  • PDF attachment ≤ 10MB (client-enforced).
  • Part of the agency tier (verify plan specifics).

Technical implementation

  • Views: frontend/src/views/agency/client-invitations/{Create,List}.vue
  • Routes: frontend/src/router/agency.js (agency-client-invitations, agency-client-invitation-create)
  • Repository: frontend/src/repositories/agency/clientInvitations.js
  • Controller: backend/app/controllers/ClientInvitationController.js — see backend service
  • Model: backend/app/models/ClientInvitationModel.js
  • Email: white-label send via the platform email queue, template agency/client-invitation.html; branding from AgencyModel