> ## Documentation Index
> Fetch the complete documentation index at: https://help.propops.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Email Configuration

> Set up email delivery, configure Brevo, customise templates, and manage environment variables in PropOps Web.

This guide covers the developer setup for email delivery in PropOps Web — including sending methods, template customisation, and environment configuration.

***

## Choosing a sending method

PropOps Web supports two email sending methods. Each template can be configured independently.

| Method               | When to use                                                                                                                      |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| **Local (built-in)** | Development, self-hosted, or when you don't need Brevo. Sends via PHP `mail()` or Mailpit. Works out of the box with no API key. |
| **Brevo API**        | Production environments where you want deliverability tracking, domain verification, and analytics. Requires a `BREVO_API_KEY`.  |

<Info>
  You can mix both methods — use Brevo for customer-facing emails and local templates for internal notifications.
</Info>

***

## Setting up local email

Local email works immediately with no configuration. Templates are rendered from `views/emails/` and sent via PHP `mail()`.

<Steps>
  <Step title="Set your sender details">
    In your `.env` file:

    ```env theme={null}
    MAIL_FROM_ADDRESS=noreply@yourcompany.com
    MAIL_FROM_NAME=Your Company Name
    ```
  </Step>

  <Step title="Configure your branding">
    Set these so templates display your logo and colour:

    ```env theme={null}
    APP_NAME=Your Company
    APP_URL=https://yourcompany.com
    APP_LOGO_PATH=/assets/images/logo.png
    BRAND_PRIMARY_COLOR=#0071e3
    ```
  </Step>

  <Step title="Leave BREVO_API_KEY empty">
    When no Brevo key is set, PropOps Web automatically uses local templates:

    ```env theme={null}
    BREVO_API_KEY=
    ```
  </Step>
</Steps>

***

## Setting up Brevo

<Steps>
  <Step title="Create a Brevo account">
    Sign up at [brevo.com](https://www.brevo.com/) if you don't already have an account.
  </Step>

  <Step title="Generate an API key">
    In the Brevo dashboard, go to **SMTP & API → API Keys** and generate a new key.
  </Step>

  <Step title="Add to your environment">
    ```env theme={null}
    BREVO_API_KEY=your-api-key-here
    MAIL_FROM_ADDRESS=noreply@yourcompany.com
    MAIL_FROM_NAME=Your Company Name
    ```
  </Step>

  <Step title="Verify your sender domain">
    In Brevo, go to **Senders, Domains & Dedicated IPs** and verify the domain you're sending from. This improves deliverability and prevents emails being marked as spam.
  </Step>

  <Step title="Set template values to Brevo IDs">
    For any email you want to send via Brevo, change the template value from a name to the numeric Brevo template ID:

    ```env theme={null}
    # Local template (default)
    EMAIL_TEMPLATE_WELCOME=welcome

    # Brevo template
    EMAIL_TEMPLATE_WELCOME=30
    ```
  </Step>
</Steps>

***

## How template resolution works

Each email type is mapped to an `EMAIL_TEMPLATE_*` variable in `.env`. The value determines how PropOps Web sends that email:

| Value type                  | Behaviour                                                        |
| --------------------------- | ---------------------------------------------------------------- |
| **String** (e.g. `welcome`) | Renders the local HTML template from `views/emails/welcome.html` |
| **Number** (e.g. `30`)      | Sends via the Brevo API using that template ID                   |
| **Empty**                   | Falls back to a local template derived from the env key name     |

<Info>
  Local templates use `{"{{ params.COLUMN_NAME }}"}` syntax — the same format Brevo uses — so migrating between local and Brevo is seamless.
</Info>

***

## Customising templates

### Editing an existing template

All 47 pre-built templates live in `views/emails/`. Each template is a standalone HTML fragment that gets wrapped in `layout.html`.

<Steps>
  <Step title="Open the template file">
    Find the template in `views/emails/` — for example `welcome.html`.
  </Step>

  <Step title="Edit the HTML">
    Templates are plain HTML. Use `{"{{ params.VARIABLE_NAME }}"}` placeholders for dynamic content. Check the existing template to see which variables are available.
  </Step>

  <Step title="Test locally">
    If you're using Mailpit (via DDEV or Docker), emails will appear in the Mailpit inbox at `http://localhost:8025` so you can preview your changes.
  </Step>
</Steps>

### Creating a new template

<Steps>
  <Step title="Create the HTML file">
    Create a new `.html` file in `views/emails/`. The filename should match the template name you'll use in `.env` — for example `my_custom_email.html`.
  </Step>

  <Step title="Add a subject line">
    Include a subject in an HTML comment at the top of the file:

    ```html theme={null}
    <!-- Subject: Your Email Subject Here -->
    ```

    You can use placeholders in the subject:

    ```html theme={null}
    <!-- Subject: Job Update — {{ params.JOB_REF }} -->
    ```
  </Step>

  <Step title="Write your template content">
    Add your HTML content. The template will be automatically wrapped in `layout.html` (logo, footer, styling). You only need to write the inner content:

    ```html theme={null}
    <!-- Subject: Custom Notification -->

    <h1 style="margin: 0 0 8px 0; font-size: 22px; font-weight: 600;
               color: #1d1d1f; letter-spacing: -0.02em;"
        class="email-heading">Your heading</h1>

    <p style="margin: 0 0 28px 0; font-size: 15px; color: #424245;
              line-height: 1.6;" class="email-text">
      Hi {{ params.RECIPIENT_NAME }}, here is your notification.
    </p>

    <!-- Info box -->
    <table role="presentation" cellpadding="0" cellspacing="0" width="100%"
           style="margin: 0 0 28px 0; background-color: #f5f5f7;
                  border-radius: 12px;">
      <tr>
        <td style="padding: 20px 24px;">
          <p style="margin: 0 0 4px 0; font-size: 12px; font-weight: 600;
                    color: #86868b; text-transform: uppercase;
                    letter-spacing: 0.05em;">Label</p>
          <p style="margin: 0; font-size: 15px; color: #1d1d1f;"
             class="email-text">{{ params.VALUE }}</p>
        </td>
      </tr>
    </table>

    <!-- CTA button -->
    <table role="presentation" cellpadding="0" cellspacing="0">
      <tr>
        <td style="background-color: {{ params.BRAND_PRIMARY_COLOR }};
                   border-radius: 10px;">
          <a href="{{ params.ACTION_URL }}"
             style="display: inline-block; padding: 13px 28px;
                    font-size: 15px; font-weight: 600; color: #ffffff;
                    text-decoration: none;">View Details</a>
        </td>
      </tr>
    </table>
    ```
  </Step>

  <Step title="Register in .env">
    Add a new `EMAIL_TEMPLATE_*` variable pointing to your template name:

    ```env theme={null}
    EMAIL_TEMPLATE_MY_CUSTOM_EMAIL=my_custom_email
    ```
  </Step>

  <Step title="Send from code">
    Use `EmailService::sendTemplate()` with the env key:

    ```php theme={null}
    $templateRef = $emailService->getTemplateId('MY_CUSTOM_EMAIL');
    $emailService->sendTemplate($templateRef, $recipient, $params);
    ```
  </Step>
</Steps>

### Editing the layout

The shared layout in `views/emails/layout.html` wraps every template. It contains:

* The outer email structure (background, centring)
* Your brand logo (centred)
* The white content card
* The footer with `APP_URL` and copyright
* Dark mode styles
* Responsive breakpoints

Edit `layout.html` to change the look of all emails at once. The placeholder `{"{{ TEMPLATE_CONTENT }}"}` is where each template's content is injected.

### Auto-injected variables

These variables are available in every template automatically:

| Variable              | Source                           |
| --------------------- | -------------------------------- |
| `APP_NAME`            | From `.env`                      |
| `APP_URL`             | From `.env`                      |
| `APP_LOGO_PATH`       | From `.env`                      |
| `BRAND_PRIMARY_COLOR` | From `.env` (default: `#2563eb`) |
| `CURRENT_YEAR`        | Auto-generated                   |

***

## Environment configuration

### Core settings

```env theme={null}
BREVO_API_KEY=           # Optional — leave empty for local-only sending
MAIL_FROM_ADDRESS=noreply@yourcompany.com
MAIL_FROM_NAME=Your Company Name
```

### Template mappings

Each email type has an `EMAIL_TEMPLATE_*` variable. Set it to a **local template name** or a **Brevo template ID**:

```env theme={null}
# Jobs
EMAIL_TEMPLATE_JOB_CREATED=job_created
EMAIL_TEMPLATE_JOB_UPDATE=job_update
EMAIL_TEMPLATE_JOB_UPDATED=job_updated
EMAIL_TEMPLATE_JOB_UPDATED_AGENT=job_updated_agent
EMAIL_TEMPLATE_JOB_UPDATED_CONTRACTOR=job_updated_contractor
EMAIL_TEMPLATE_JOB_RECALLED=job_recalled
EMAIL_TEMPLATE_JOB_RECALL_COMPLETED=job_recall_completed
EMAIL_TEMPLATE_JOB_RECALL_CANCELLED=job_recall_cancelled
EMAIL_TEMPLATE_QUOTE_READY=quote_ready
EMAIL_TEMPLATE_JOB_COMPLETED=job_completed
EMAIL_TEMPLATE_JOB_STATUS_REMINDER=job_status_reminder
EMAIL_TEMPLATE_JOB_TYPE_CREATED=job_type_created
EMAIL_TEMPLATE_JOB_TYPE_STATUS_CHANGED=job_type_status_changed

# Users & Authentication
EMAIL_TEMPLATE_WELCOME=welcome
EMAIL_TEMPLATE_EMAIL_ACTIVATION=email_activation
EMAIL_TEMPLATE_PASSWORD_RESET=password_reset
EMAIL_TEMPLATE_PASSWORD_CHANGED=password_changed
EMAIL_TEMPLATE_USER_CREATED=user_created
EMAIL_TEMPLATE_USER_DELETED=user_deleted
EMAIL_TEMPLATE_USER_STATUS_CHANGED=user_status_changed
EMAIL_TEMPLATE_USER_ROLE_CHANGED=user_role_changed
EMAIL_TEMPLATE_USER_BLACKLISTED=user_blacklisted
EMAIL_TEMPLATE_USER_UNBLACKLISTED=user_unblacklisted
EMAIL_TEMPLATE_ONBOARDING_REMINDER=onboarding_reminder
EMAIL_TEMPLATE_LOGIN_IP_VERIFICATION_PIN=login_ip_verification_pin

# Financial
EMAIL_TEMPLATE_INVOICE_READY=invoice_ready
EMAIL_TEMPLATE_PAYMENT_UPDATE=payment_update

# Notifications & Communication
EMAIL_TEMPLATE_CASE_NOTE=case_note
EMAIL_TEMPLATE_CASE_NOTE_ADDED=case_note_added
EMAIL_TEMPLATE_APPOINTMENT_REMINDER=appointment_reminder
EMAIL_TEMPLATE_CERTIFICATE_REMINDER=certificate_reminder
EMAIL_TEMPLATE_DOCUMENT_SHARED=document_shared
EMAIL_TEMPLATE_ATTACHMENT_UPLOADED=attachment_uploaded

# Media
EMAIL_TEMPLATE_VIDEO_READY=video_ready
EMAIL_TEMPLATE_VIDEO_FAILED=video_failed

# Security Alerts
EMAIL_TEMPLATE_PASSWORD_BREACH_DETECTED=password_breach_detected
EMAIL_TEMPLATE_PASSWORD_BREACH_RESET=password_breach_reset
EMAIL_TEMPLATE_WEBHOOK_TOKEN_SECURITY_ALERT=webhook_token_security_alert
EMAIL_TEMPLATE_BRUTE_FORCE_ACCOUNT_LOCKED=brute_force_account_locked

# System & Infrastructure
EMAIL_TEMPLATE_DEPLOYMENT_NOTIFICATION=deployment_notification
EMAIL_TEMPLATE_API_ENDPOINT_FAILURE=api_endpoint_failure
EMAIL_TEMPLATE_API_DISABLED=api_disabled
EMAIL_TEMPLATE_API_NOT_RESPONDING=api_not_responding
EMAIL_TEMPLATE_API_CREATED=api_created
EMAIL_TEMPLATE_API_RATE_LIMIT_EXCEEDED=api_rate_limit_exceeded
EMAIL_TEMPLATE_BRANCH_STATUS_CHANGED=branch_status_changed
```

<Tip>
  To switch any email to Brevo, replace the template name with the numeric Brevo template ID (e.g. `EMAIL_TEMPLATE_WELCOME=30`). To switch back to local, set it to the template name (e.g. `EMAIL_TEMPLATE_WELCOME=welcome`).
</Tip>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Emails not being sent">
    * If using Brevo, check that `BREVO_API_KEY` is set correctly in your `.env`
    * If using local, check your server's `mail()` function is working (`php -r "mail('test@example.com','Test','Body');"`)
    * Verify the sender domain is confirmed in Brevo (if applicable)
    * Check the **Email Log** in **Admin → Email Log** for error messages
  </Accordion>

  <Accordion title="Emails going to spam">
    * Ensure your sender domain has SPF, DKIM, and DMARC records configured
    * Verify the domain in Brevo's sender settings
    * Use a professional sender address (avoid free email providers)
  </Accordion>

  <Accordion title="Template not rendering correctly">
    * Check the template file exists in `views/emails/` with the correct filename
    * Verify placeholder names match exactly (case-sensitive): `{"{{ params.USER_NAME }}"}` not `{"{{ params.user_name }}"}`
    * If the template falls back to the generic template, the specific file may be missing or misspelt
    * Use Mailpit to preview rendered HTML during development
  </Accordion>

  <Accordion title="Switching between local and Brevo">
    * To use a local template: set the env value to the template name (e.g. `welcome`)
    * To use Brevo: set the env value to the numeric Brevo template ID (e.g. `30`)
    * You can mix both — each template is independent
  </Accordion>
</AccordionGroup>
