Engagement Hub — Outbound email (technical)
Technical reference for informational outbound email via the Hub POST /start API, email templates, placeholders, and branding.
Audience: Integrators, bulk workers, CRM custom apps, automation engineers.
Canonical client guide: Engagement Hub Start API — share with clients; includes /integration/start, /start, and full email_body_html documentation. Update that file when email start fields change.
Related user guide: Aventora User Manual — §3.11 Account Settings (Outbound Email tab).
Overview
| Layer | Responsibility |
|---|---|
| aventora-admin | SMTP settings, header/footer/logo branding, template library (account + optional per-user) |
| Aventora-Assistant (Hub) | POST /start with channel: "email", template resolution, placeholder fill, SMTP send |
| domain-chatbot | Domain logo asset (GET /stats/logo/{domain_name}) when “Include domain logo” is enabled |
Email channel supports informational and interactive sessions (conversational, booking, confirmational, and other types). Every /start email opens an email_sessions row and sets Reply-To so a customer reply can continue the thread. Informational email is marked Done after a successful send; a later reply reopens it and upgrades to conversational (same rule as informational SMS). Appointment-booking and other text-session state (stored_slots, booking flags) is merged into that row’s conversation_context so a later “book that time” reply can still match the offered slots. SMS/WhatsApp/CLI keep using sms_sessions. Availability for today starts from now (not the start of the working day). If a booking is rejected, Hub looks up remaining times and includes them in the same reply — it does not send a later “checking availability” email.
Prerequisites
- Email settings configured in Admin → Engagement Hub → Account Settings → Email (Google/Microsoft linked mailbox, System, or legacy SMTP).
domain_nameon the/startrequest must match the tenant domain.- Hub API key with permission to initiate calls (
/start). - Engagement hub not paused (
engagement_hub_paused).
For notification round-robin out-of-office pause, Hub reads auto-replies from the same send channel: Google/Microsoft linked inbox, or (when Email settings use System) domain-chatbot SysInbound IMAP (SYS_INBOUND_* env). Email Pull is not used as a fallback inbox.
A reply whose body is only PAUSE or RESUME (or OUT / BACK) from a notification recipient pauses or resumes that address until the next office day allows another change. An admin can still resume them the same day. Out-of-office and bounce mail is not treated as that keyword.
For System/SES email (all types, including informational), Hub sets Reply-To to the account engagement inbound IMAP mailbox when that setting is complete. Otherwise it uses platform SES inbound receiving (eh{token}@inbound.aventora.ai when SES_INBOUND_* is configured on domain-chatbot) and polls that S3 bucket. After Hub matches a token to a local session and persists the reply (with its RFC Message-ID), it deletes that S3 object. An unmatched or failed attempt does not keep the Message-ID as processed, so a later poll can retry. A Hub crash before persist can reclaim a stale Message-ID lease after 15 minutes. Mail for other Hub databases is left in the bucket. If SES inbound is not configured, it plus-addresses SysInbound. Start fails if none of these is available. Customers must Reply (to Reply-To); sending to the SES From address may never arrive. This is not Email Pull.
System/SES visible From is {display name} <SMTP_FROM_EMAIL> (for example John Smith <aws-api@aventora.ai>). The display name may be the agent; the address stays the configured SES sender. inbound.aventora.ai is receive-only. MAIL FROM / Return-Path is independent of Reply-To and is not used for session matching. Do not configure custom MAIL FROM on inbound.aventora.ai. See SES inbound receiving.
Optional: per-user SMTP/branding/templates when outbound_email_user_overrides_enabled is true and user_id is sent.
POST /start — email
Authentication: Authorization: Bearer <hub_api_key>
Informational email still delivers a one-way first message, then waits for a reply on the same thread. Every type requires a reply mailbox (Google/Microsoft same inbox, or System inbound IMAP / SES inbound / SysInbound).
Required fields
| Field | Description |
|---|---|
channel | "email" |
type | "informational" for one-shot, or an interactive type such as "conversational" |
domain_name | Tenant slug (e.g. aventora) |
email_address or client_email | Recipient (validated format) |
Common optional fields
| Field | Description |
|---|---|
user_id | Hub user id — per-user SMTP/templates/branding when overrides enabled |
client_name | {{CUSTOMER_NAME}} (fallback: recipient email) |
email_subject | Subject line and {{SUBJECT}} value |
instruction | See Send paths below |
email_body_html | Inline HTML body with {{PLACEHOLDER}} tags (mutually exclusive with email_template_id / email_template_name) |
email_template_id | UUID of a saved template (Admin or API) |
email_template_name | Template name if id omitted |
email_template_params | Object of extra placeholder values (this document) |
from_name | Optional From display name. Overrides account From Name for this send. CRM sets this to Profile name, default initiator, or workspace display name. |
phone_number | Optional. Stored on call_logs.phone_number when the Person has a phone. Informational, pull, and conversational email store n/a when omitted. Connect-now is skipped when the stored number is n/a. |
language | ISO-ish code; used only when no template (LLM body), default en |
Fields may be top-level or nested under body (Hub accepts both).
Legacy alias
template_params is accepted as an alias for email_template_params.
Placeholders
Built-in (always set by Hub)
| Placeholder | Source |
|---|---|
{{SUBJECT}} | email_subject if provided; otherwise a subject Hub writes from the engagement instruction and type |
{{CUSTOMER_NAME}} | client_name, else recipient email, else "Customer" |
{{ORGANIZATION_NAME}} | Account setting organization_name for the domain |
Built-in keys cannot be overridden via email_template_params.
Optional extra parameters (email_template_params)
Pass a JSON object on /start. Each key becomes an uppercase placeholder:
| Request key | Placeholder in template |
|---|---|
policy_number | {{POLICY_NUMBER}} |
renewal_date | {{RENEWAL_DATE}} |
agent_name | {{AGENT_NAME}} |
Rules:
- Keys must match
^[A-Za-z][A-Za-z0-9_]*$(letters, digits, underscore; must start with a letter). - Values are converted to strings; empty strings are skipped.
- Reserved keys
SUBJECT,CUSTOMER_NAME,ORGANIZATION_NAMEinemail_template_paramsare ignored. - Substitution runs in template subject, template body, and branding header/footer HTML.
Example request
{
"channel": "email",
"type": "informational",
"domain_name": "aventora",
"email_address": "customer@example.com",
"client_name": "Alex Morgan",
"email_subject": "Your policy renewal",
"email_template_id": "550e8400-e29b-41d4-a716-446655440000",
"email_template_params": {
"policy_number": "POL-2026-8842",
"renewal_date": "June 15, 2026",
"agent_name": "Jamie Lee"
}
}
Example template body snippet:
<p>Dear {{CUSTOMER_NAME}},</p>
<p>Policy <strong>{{POLICY_NUMBER}}</strong> renews on {{RENEWAL_DATE}}.</p>
<p>Your advisor {{AGENT_NAME}} can answer any questions.</p>
<p>Best regards,<br/>{{ORGANIZATION_NAME}}</p>
Send paths
Conflict rule: do not send email_body_html together with email_template_id or email_template_name — Hub returns 400.
A — Inline HTML (email_body_html)
fill_email_templatereplaces placeholders inemail_body_htmlandemail_subject(subject defaults to{{SUBJECT}}when omitted).instructionis not used for body generation. Ifemail_subjectis omitted, Hub still writes a subject from the instruction and engagement type for{{SUBJECT}}.- Hub sends that HTML as the full message document (no branding header/footer/logo wrap). Include your own
<style>, layout, and optional<html>/<body>. - SMTP send.
B — Template selected (email_template_id or email_template_name)
- Load template from
data/email_templates/{domain}.json(or per-user file if applicable). fill_email_templatereplaces all placeholders (built-in +email_template_params).instructiondoes not generate the body. If the template subject uses{{SUBJECT}}andemail_subjectis omitted, Hub writes a subject from the instruction and engagement type.- Wrap with branding (header, footer, and CID inline logo when the sender can attach images). System/SES skips CID so Gmail does not show a broken empty logo box.
- SMTP send.
If the template is not found, Hub falls through to path C.
C — No inline HTML or template (or template not found)
instructionis treated as a brief.- LLM (
expand_instruction_to_email_body) writes a short plain-text paragraph. - Wrapped in
<p>…</p>. Subject comes fromemail_subjectif provided; otherwise Hub writes one from the engagementinstructionandtype(not a fixed “Message from your provider”). - Branding wrap still applies;
email_template_paramsstill fill header/footer placeholders only.
Example — inline HTML
{
"channel": "email",
"type": "informational",
"domain_name": "aventora",
"email_address": "customer@example.com",
"client_name": "Alex Morgan",
"email_subject": "Your policy renewal",
"email_body_html": "<p>Dear {{CUSTOMER_NAME}},</p><p>Policy <strong>{{POLICY_NUMBER}}</strong> renews on {{RENEWAL_DATE}}.</p><p>Regards,<br/>{{ORGANIZATION_NAME}}</p>",
"email_template_params": {
"policy_number": "POL-2026-8842",
"renewal_date": "June 15, 2026"
}
}
Bulk CSV uploads may include an email_body_html column for channel=email rows (no instruction required when inline HTML or a template reference is present).
Priority when sources are present: email_body_html → saved template → instruction (LLM). Only one body source per request; inline HTML and template id/name cannot be combined.
Branding
Configured in Admin → Outbound Email (account or per-user):
- Custom header HTML
- Custom footer HTML
- Domain logo and/or custom logo URL
Templates store body content only; branding wraps template and instruction-generated sends. Inline email_body_html is the full document and is not wrapped.
Logo is attached as an inline CID image when the mailbox can carry inline attachments (Google, Microsoft, or SMTP). System (platform SES) cannot attach CID images, so Hub keeps the organization name and omits the broken logo box. Platform SMTP also sends a text/plain alternative beside the HTML so Gmail is less likely to treat the message as Promotions. Inbox subjects written from the engagement avoid campaign phrasing such as “Ready to…” or “Welcome from…”. Appointment-booking replies look up the next available slots instead of asking the customer to pick a date first, and sign off with the organization name (never [Your Name]).
Template storage API (Hub)
Proxied via aventora-admin for browser users; integrators may call Hub directly with the account API key.
| Method | Hub path | Purpose |
|---|---|---|
| GET | /account-settings/email-templates | List templates |
| POST | /account-settings/email-templates | Create |
| PUT | /account-settings/email-templates | Update |
| DELETE | /account-settings/email-templates | Delete |
| GET | /account-settings/email-template-catalog | System library |
| POST | /account-settings/send-test-email | SMTP + template test |
Query/body may include external_user_id when using user-scoped templates.
Call log behavior
phone_numberstores the customer phone when one was provided. Email without a customer phone, including conversational email, storesn/a. Connect-now is skipped when the stored number isn/a.- Interactive replies strip quoted Gmail/Outlook thread text before the bot and transcript.
- When the customer is ready to talk and a customer phone is on file, Hub initiates the same conference path as SMS (call the agent, then the customer).
- Status moves to Done with outcome delivered after successful SMTP for informational sends.
- Failed SMTP → Failed with reason.
Errors (common)
| HTTP | Meaning |
|---|---|
| 400 | Missing email, invalid type/channel combo, missing domain_name, unknown template (if strictly required by caller) |
| 400 | SMTP not configured |
| 403 | DNC / auth |
| 503 | Engagement hub paused |
CRM (Aventora CRM / Quick Aventora)
User guide: Aventora User Manual §2.5, §3.11.1. CRM integration detail: Outbound Email Templates.
CRM does not store Hub templates. It lists layouts from Hub and passes selections on start.
UI flow
- Person → Engage by Aventora → informational quick template.
- Channel =
email. - CRM calls
GET /rest/aventora/email-templates(workspace JWT). - User picks Email layout, subject, and dynamic template fields.
- CRM calls
POST /rest/aventora/start-engagement→ HubPOST /integration/start→ internalPOST /start.
CRM REST — list templates
Authentication: CRM workspace JWT (Authorization: Bearer …).
| Method | Path | Response |
|---|---|---|
| GET | /rest/aventora/email-templates | { templates: [{ id, name, subjectTemplate, customPlaceholderKeys }] } |
Server proxies Hub GET /account-settings/email-templates?domain_name=… using the workspace application variable AVENTORA_API_KEY (account-scoped Hub key). Requires outbound email configured for the domain (SMTP); otherwise Hub may return 400 and CRM returns an empty list.
CRM REST — start with layout
Authentication: CRM workspace JWT.
| CRM JSON field | Hub /integration/start → /start |
|---|---|
clientName | client_name → {{CUSTOMER_NAME}} |
emailSubject | email_subject → {{SUBJECT}} (optional; Hub writes one from the engagement if omitted) |
emailBodyHtml | email_body_html (mutually exclusive with template id/name) |
emailTemplateId | email_template_id |
emailTemplateName | email_template_name |
emailTemplateParams | email_template_params (keys → {{KEY}}) |
CRM also sends from_name (optional REST fromName, otherwise Settings → Profile first + last name, otherwise the default initiator Profile name, otherwise that initiator's CRM user account name, otherwise the workspace display name). REST start/bulk capture the caller name before system auth. That becomes the From display name and overrides Hub Account Settings From Name for that send. If CRM cannot resolve a name, Hub keeps its usual From Name / linked-mailbox name / platform EMAIL_DISPLAY_NAME. Partner bulk (POST /rest/aventora/start-engagement-bulk and Hub POST /bulk-calls/submit) uses the same field, including campaign-level from_name. Laravel htmlBody is accepted as emailBodyHtml.
personId, channel, type, instruction, contactIdentifier behave as for phone/SMS. For email, contactIdentifier is the recipient address. If clientName is omitted, the CRM server derives it from the person name fields when present.
CRM server environment
| Variable | Where | Purpose |
|---|---|---|
AVENTORA_BASE_URL | CRM server .env | Hub root URL for /integration/start and template proxy |
AVENTORA_API_KEY | CRM workspace application settings | Bearer token for Hub (per account) |
| Assigned domain | CRM workspace application settings | domain_name for Hub |
CRM frontend environment (optional)
| Variable | Default |
|---|---|
REACT_APP_AVENTORA_EMAIL_TEMPLATES_ENDPOINT | {REACT_APP_SERVER_BASE_URL}/rest/aventora/email-templates |
Usually unset when CRM UI and API share one host. Override only for split-domain deployments.
Workflows
Use POST /rest/aventora/start-engagement with the same JSON body and workflow-appropriate auth. Map workflow variables into emailTemplateParams. No dedicated Aventora workflow action ships in the repo today.
Version note
- 2026-09-28 — Email starts can include
email_attachments(JPEG, PNG, GIF, or WebP; 5 images, 4 MB each, 8 MB combined). Hub sends them as downloadable files on System, Google, Microsoft, and SMTP. CRM bulk stores the list once on the batch. Branding CID logos are unchanged. - 2026-09-25 — Conversational email can start without a customer phone. Hub stores
n/aand skips connect-now until a phone number exists. - 2026-09-24 — Notification email recipients can reply
PAUSEorRESUMEonce per office day on the same mailbox that watches for out-of-office. - 2026-09-21 — Partner bulk email From: CRM REST keeps the user/Profile name through system auth, falls back to the default initiator CRM user account name, and accepts Laravel
htmlBody. Hub bulk ingest mapshtmlBody/html_bodytoemail_body_html. - 2026-09-19 — Partner bulk email From: CRM API-key sends fall back to workspace display name and optional
fromName. Hub campaignfrom_namefills missing rows; queued/startusesfrom_nameplusexternal_user_idfor user/account From Name. - 2026-09-18 — Bulk ingest copies row
from_nameonto queuedrequest_json(CRM/API bulk email). Queue replay and timeout retries already forwarded that field to/start. - 2026-09-17 — Inbound Message-ID claims expire after 15 minutes. A stale claim is reclaimed only if that Message-ID is not already stored on the session message.
- 2026-09-17 — Unmatched SES inbound Message-IDs are released so a later poll can retry. S3 delete happens only after the reply is persisted locally.
- 2026-09-17 — System From is
{display name} <SMTP_FROM_EMAIL>.inbound.aventora.aiis receive-only Reply-To. MAIL FROM / Return-Path is not used for reply matching. - 2026-09-17 — Informational email uses the same Reply-To mailbox as other types. A customer reply reopens the engagement and upgrades it to conversational (same rule as informational SMS).
- 2026-09-16 — After a local session consumes an SES inbound reply, Hub deletes that S3 object. Unmatched objects stay for other Hub instances.
- 2026-09-16 — Appointment-booking email looks up remaining times from now (not morning slots already gone). If a booking is rejected, that same reply includes the next remaining times instead of asking the customer to wait for another email.
- 2026-09-16 — Appointment-booking email persists offered slots on
email_sessionsso a later “book that time” reply can complete the calendar booking. - 2026-09-16 — Appointment-booking email looks up the next available slots when the customer asks for a time; replies sign off with the organization name. System/SES omits CID logos and platform SMTP includes a plain-text alternative to reduce Promotions placement.
- 2026-09-16 — When
email_subjectis omitted, Hub writes the inbox subject from the engagement instruction and type. Header/footer branding is unchanged. Explicitemail_subjectand saved template subject patterns still win. - 2026-09-16 — Conversational email sends immediately and does not wait on voice call hours. Call hours apply later only when Hub places the connect-now conference. The Admin Contact column shows the recipient email.
- 2026-09-16 — Conversational email requires a customer phone and stores it on
call_logs.phone_number. Hub places a connect-now conference when the customer is ready. Quoted reply threads are stripped from the transcript. - 2026-09-16 — System/SES email uses account engagement inbound IMAP, otherwise platform SES inbound receiving (
eh{token}@inbound.aventora.ai), otherwise plus-addressed SysInbound. - 2026-09-16 — CRM-originated email can send
from_name(Profile full name). Hub uses it as the From display name and overrides account From Name for that send. Queued bulk and timeout retries copyfrom_name. - 2026-09-14 — Inline
email_body_html(including CRM bulkemailBodyHtml) is the full HTML document after placeholder fill. Hub does not wrap it in account branding. Saved templates and instruction/LLM bodies still use branding wrap. Bulk queue replay copiesemail_body_htmlontoPOST /start. - 2026-07-18 — System-sender notification OOO uses domain-chatbot SysInbound IMAP (
SYS_INBOUND_*); Email Pull is not a fallback inbox. email_template_params— custom placeholders on informational email sends.- CRM UI +
GET /rest/aventora/email-templates— Quick Aventora email layout picker (aventora-crmmain).
Documented in Engagement Hub Start API (canonical), this file, Aventora User Manual §2.5 / §3.11.1, and Outbound Email Templates.