[![Actions Status](https://github.com/librasteve/Air-Plugin-MailForm/actions/workflows/test.yml/badge.svg)](https://github.com/librasteve/Air-Plugin-MailForm/actions)

# Air::Plugin::MailForm

A plugin for the Raku [Air](https://raku.land/zef:librasteve/Air) web framework that renders a contact form and sends submissions via SMTP.

## Synopsis

```raku
#!/usr/bin/env raku

use Air::Functional :BASE;
use Air::Base;
use Air::Plugin::MailForm;

my $site =
site :register[$mail-form, LightDark.new],
    page
        main [
            h3 'Contact Us';
            $mail-form;
        ]
;

$site.serve;
```

Set environment variables before running:

```sh
export SMTP_USER=myuser
export SMTP_PASS=mypass
export MAIL_FROM=webserver@example.com
export MAIL_TO=contact@example.com
raku bin/29-mailform.raku
```

## Description

`Air::Plugin::MailForm` is a two-class plugin. `Air::Plugin::MailForm` is the field-free base that provides SMTP sending and form lifecycle (validate → send → finish/retry). `Air::Plugin::ContactForm` is the built-in subclass that adds name, email, and message fields. The exported `$mail-form` singleton is a `ContactForm` instance, so most users need nothing more than `use Air::Plugin::MailForm`.

SMTP credentials and routing are configured entirely through environment variables so no secrets appear in source code.

## Custom Fields

Subclass `Air::Plugin::MailForm` to define your own fields. Override `method mail-body` to control the email body text:

```raku
use Air::Plugin::MailForm;
use Air::Form;

class EnquiryForm is Air::Plugin::MailForm {
    has Str $.company is required;
    has Str $.phone   is tel;
    has Str $.message is multiline;

    method mail-message(Str $from, Str $to, Str $subject --> Str) {
        qq:to/END/;
        From:    $from
        To:      $to
        Subject: $subject

        Company: { $.company }
        Phone:   { $.phone   }

        { $.message }
        END
    }

    method form-routes {
        self.init;
        self.submit: -> EnquiryForm $form {
            self.submit-form($form)
        }
    }
}

our $enquiry-form = EnquiryForm.empty;
# site :register[$enquiry-form], ...
# page main $enquiry-form;
```

## Environment Variables

| Variable       | Default                   | Description             |
|----------------|---------------------------|-------------------------|
| `SMTP_HOST`    | `mail-eu.smtp2go.com`     | SMTP server hostname    |
| `SMTP_PORT`    | `587`                     | SMTP server port        |
| `SMTP_USER`    | —                         | SMTP username           |
| `SMTP_PASS`    | —                         | SMTP password           |
| `MAIL_FROM`    | —                         | Envelope sender address |
| `MAIL_TO`      | —                         | Recipient address       |
| `MAIL_BCC`     | —                         | BCC address (optional)  |
| `MAIL_SUBJECT` | `Contact Form Submission` | Email subject line      |

## Docker

Pass the variables at `docker run` time with `-e` flags — never bake secrets into the image:

```sh
docker run -p 3000:3000 \
  -e SMTP_USER=myuser \
  -e SMTP_PASS=mypass \
  -e MAIL_FROM=webserver@example.com \
  -e MAIL_TO=contact@example.com \
  myimage
```

For multiple containers or CI pipelines an env file is tidier:

```sh
# .env  (add to .gitignore — never commit this)
SMTP_USER=myuser
SMTP_PASS=mypass
MAIL_FROM=webserver@example.com
MAIL_TO=contact@example.com
```

```sh
docker run -p 3000:3000 --env-file .env myimage
```

## Installation

If you already have [Air](https://raku.land/zef:librasteve/Air) working, then:

 - `zef install Air::Plugin::MailForm`

Otherwise, follow the Air::Examples [Getting Started](https://github.com/librasteve/Air-Examples/blob/main/README.md#getting-started)

 - `cd Air-Examples`
 - `raku bin/29-mailform.raku`
 - point browser to `http://localhost:3000`

## Author

librasteve <librasteve@furnival.net>

## Copyright and License

Copyright 2026 Stephen Roe.

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.
