horilla.contrib.mail handles outbound templates, SMTP/Outlook configuration, open tracking, and works with mail-to-lead in horilla_crm.leads — integrated with automations and cadences.

This post is Part 12 of 28 in the Horilla CRM Technical Blog series.

What is the Horilla mail module?

The Horilla mail module enables you to:

  • HTML email templates with merge fields
  • Per-company SMTP settings
  • HorillaMailManager for programmatic send
  • Inbound email creating Lead records

Mail domain architecture

Horilla CRM keeps email infrastructure in horilla.contrib.mail, rather than in a standalone horilla_mail application. The app separates persisted messages, account configuration, reusable templates, and file attachments so that a message is both operationally deliverable and auditable after it has been sent or received. The central HorillaMail model represents an individual mail record. It stores transport and presentation metadata such as sender, recipients, subject, body, status, and timestamps, while HorillaMailAttachment represents files associated with that record.

The model relationship is deliberately generic at the CRM boundary. HorillaMail uses a GenericForeignKey, allowing a message to be associated with a lead, contact, opportunity, account, or another supported business object without adding a mail foreign key to each domain model. This is important for activity timelines: one rendering path can request messages for any record type by content type and object ID. Avoid duplicating mail content into application-specific models; the generic relation is the canonical association.

ComponentResponsibilityOperational implication
HorillaMailMail record and generic CRM associationDisplay in tabs, timelines, and audit views
HorillaMailConfigurationSMTP or provider and OAuth configurationConfigure per supported sending identity
HorillaMailTemplateReusable subject and body definitionUse controlled placeholders and permissions
HorillaMailAttachmentAttachment metadata and file referenceAttach through the message, not a lead
HorillaMailManagerDelivery and mail construction serviceCentralize sending behavior

Sending through the mail service

Application code should delegate mail creation and delivery to HorillaMailManager in horilla.contrib.mail.services. A manager provides one place to resolve the active configuration, render a selected HorillaMailTemplate, create a durable HorillaMail record, attach files, and invoke the configured backend. Direct use of Django’s mail helpers bypasses this record lifecycle and leaves an email absent from CRM history.

The default backend is HorillaDefaultMailBackend. It is the integration boundary for transport: callers ask the service to send a CRM message rather than handling SMTP settings in every workflow. Configuration belongs in HorillaMailConfiguration, including credentials or provider-specific authorization material. Secrets must be handled as configuration data and never embedded in a template, Celery task argument, or model additional_info.

from horilla.contrib.mail.services import HorillaMailManager
mail = HorillaMailManager.send_mail(
    configuration=configuration,
    recipients=["prospect@example.com"],
    subject="Follow-up",
    body="<p>Thanks for your time.</p>",
    related_object=lead,
)

The exact service arguments can evolve, but the boundary should not: resolve the configuration, pass the business object explicitly, and retain the returned HorillaMail instance for subsequent attachment or status handling. When sending from an automation or view, enqueue work only after the transaction commits so a worker cannot send a message referring to a rolled-back record.

from horilla.db import transaction
transaction.on_commit(
    lambda: HorillaMailManager.send_mail(
        configuration=configuration,
        recipients=[lead.email],
        subject=rendered_subject,
        body=rendered_body,
        related_object=lead,
    )
)

Templates, permissions, and tracking

HorillaMailTemplate makes common outbound messages reusable while preserving a controlled authoring surface. Templates should be selected by an allowed user and rendered using data already authorized for the target record. Keep template variables small, documented, and defensive: an optional field must have a fallback so one incomplete contact does not fail a batch campaign. The feature registration key for template capabilities is mail_template; deployments that add a template UI should verify this feature is registered before treating missing permissions as a view bug.

Open tracking is implemented through TrackOpenView. A rendered HTML message may contain a tracking resource whose request records an open against the appropriate HorillaMail. Treat an open as an approximate engagement signal, not proof that a person read the message: client image blocking, privacy proxies, and automatic prefetching all affect it. Do not use a tracking request to expose message content or to authorize access; it should identify only the tracking record and return a safe tracking response.

Mail-to-lead ingestion

Inbound conversion belongs to horilla_crm.leads. EmailToLeadConfig defines the configured behavior for turning qualifying incoming messages into leads, and fetch_emails_to_leads performs the retrieval and conversion workflow. Keep these responsibilities out of horilla.contrib.mail: the mail application handles message transport and persistence; the leads application owns lead creation policy, ownership, source values, and duplicate behavior.

The fetch job should be idempotent. Persist enough provider identity or message metadata to ensure the same inbound message cannot create multiple leads after retries. Before enabling automatic conversion, test mailbox selection, sender extraction, company assignment, and duplicate matching with representative emails. A broad rule that turns every system notification into a lead can quickly pollute the pipeline.

Outlook OAuth integration

Outlook authorization is a configuration concern, not a browser-only convenience. The OAuth callback must validate state, exchange the authorization code securely, and store refreshed authorization details with the applicable HorillaMailConfiguration. Request only the Microsoft scopes needed for the selected operation, keep redirect URLs environment-specific, and make token refresh failures observable to administrators without logging tokens. A practical operational check is to test one interactive send, one background send after token refresh, and one inbound fetch before declaring the connection healthy.

Benefits of Email in Horilla CRM

  • Unified email from UI, automations, and cadences
  • Template management without code deploys
  • Capture website and email inquiries as leads

Configure SMTP and templates once, then send from workflows, cadences, and user actions through HorillaMailManager.

Continue the series

Previous: Part 11 — A Complete Guide to KPI Widgets and ECharts in Horilla CRM

Next: Part 13 — Activities in Horilla CRM: Calls, Meetings, Tasks, and the Activity Tab

More posts are at Horilla Blogs; share feedback on GitHub.

Share this article