# Shipping Templates with a Module

https://dev.wisecp.com/es/shipping-templates-with-a-module

Ship a notification with your module, so enabling it is all the operator has to do.

## Overview

A module that sends its own mail installs two things. The **registration** puts the template on the Notification Templates screen, where the operator turns it on or off. The **files** put the subject, the body and the text message where they are read from.

Skip the first and `dispatch()` returns `disabled`. Skip the second and the mail goes out with no body and no subject. One call does both.

- **Notification::seed_templates()**: Adds the missing registration and writes the parts to their roots. An existing file is never overwritten, so an operator's edit survives every re-enable.
- **Notification::dispatch()**: Sends it afterwards. A seeded template is dispatched like a core one.

## Prerequisites

- A module with an `enable()` path.
- A group name. Reuse a core group when the mail belongs to that domain, or use your own.
- Read [How Notification Templates Work](https://dev.wisecp.com/en/how-notification-templates-work) first. It explains the roots this call writes into.

## Structure

Keep the templates inside the module, so they travel and are removed with it. Two source shapes are read; use whichever suits you.

```bash
coremio/modules/Servers/Acme/
├── Acme.php
└── notifications/                       # flat form: one file per part
    ├── en/
    │   ├── acme-quota-reached.json        # {"subject": "..."}
    │   ├── acme-quota-reached.html        # the mail body
    │   └── acme-quota-reached.txt         # the text message
    └── tr/ ...

coremio/modules/Servers/Acme/notifications/    # folder form: adds a per-design body
└── en/acme-quota-reached/
    ├── content.json
    ├── content.txt
    ├── content.html                       # the base design's body
    └── ledger.html                        # the Ledger design's own body
```

The folder form is what a release package uses, so a module and a release describe a template alike.

## Walkthrough

1. Write the mail body as a **fragment**. The shell around it comes from the active design.
2. Write one file per language you support. A language you skip has no message; the operator can fill it in.
3. Use Smarty placeholders (`{$service_name}`) and declare them in `settings`, so the panel offers them.
4. Call the seeder from `enable()`. Add a guard that also runs on update: an installation enabled long ago never toggles the module to receive a new template.
5. Send with `dispatch()` and read the returned `status`. `disabled` means the registration is missing or the operator turned it off.

## Reference

```php
static function seed_templates(string $group, array $templates, array $options = []): array
{
    // ...
}
```

- **$templates[key]['settings']**: The config entry, written only when the key is absent. Anything you omit is defaulted: `status` 1, `user-mail` 1, `admin-mail`/`user-sms`/`admin-sms` 0, empty `emails`/`phones`/`departments`. **Leave the key out entirely** and the config file is not touched at all — for a module that maintains its own entry.
- **$templates[key]['source']**: Directory holding the files. Both shapes above are read; the folder form is tried first because it is the one that can carry a per-design body.
- **$templates[key]['text']**: Inline alternative to `source`: `[lang => ['subject' => …, 'html' => …, 'sms' => …, 'themes' => [design => html]]]`. A part you leave out is not written.
- **$options['themes']**: `'base'` (default) writes the body to the design that ships with the product and lets every other design fall back to it. `'all'` copies that body into each installed design — see the pitfall below before choosing it.
- **return**: `['written' => string[], 'skipped' => int, 'registered' => string[]]` — paths written this run, files left alone because they already existed, and the `group/key` pairs added to the config.

## Example

```php
private function ensure_notification_template(): void
{
    static $checked = false;
    if ($checked) return;
    $checked = true;

    \Notification::seed_templates('service', [
        'acme-quota-reached' => [
            'settings' => [
                'variables' => '{service_id},{service_name},{quota_usage}',
                'status'    => 1,
                'user-mail' => 1,
            ],
            'source' => __DIR__ . DS . 'notifications',
        ],
    ]);
}
```

```php
$this->ensure_notification_template();

$result = \Notification::dispatch('service', 'acme-quota-reached', [
    'user_id'   => (int) $service['owner_id'],
    'variables' => [
        '{service_id}'   => $service['id'],
        '{service_name}' => $service['name'],
        '{quota_usage}'  => $usage . '%',
    ],
]);

// 'disabled' is a decision, not a failure: the operator turned this mail off.
if (($result['status'] ?? '') === 'error') \Logger::error('Acme quota mail failed');
```

## Pitfalls

> **A design you do not ship is not a gap**
> 
> A design with no file for the event shows the base body inside *its own* shell. Copying the body into every design is not the same thing. The copy outranks the fallback, so that theme's author can never ship a design for the mail afterwards.

> **Registration alone is not enough**
> 
> A config entry with no files sends an empty mail. The first sign of it is a customer complaint. If you maintain the entry yourself, still call the seeder for the files. Pass no `settings` key and your entry is left alone.

> **Never build the path yourself**
> 
> The parts live in different roots, and the body's root depends on the active design. Code that joins `templates/notifications/` to a language folder writes where nothing reads. The write succeeds and the mail stays empty.

## Related Articles

- [How Notification Templates Work](https://dev.wisecp.com/en/how-notification-templates-work)
- [Writing an Email Template](https://dev.wisecp.com/en/writing-an-email-template)
- [Notification Template Variables](https://dev.wisecp.com/en/notification-template-variables)
