Flick
Guide Validation Services API Pro Examples GitHub
Docs / Services
Flick Pro Service

Tested and maintained, so you don't have to. $99/year.

Get Flick Pro

Mail

Email sending service with support for multiple transports, HTML templates with variable substitution, attachments, and form data formatting.

Why Use This Service?

What you'd build yourself: SMTP connections, transport configuration for SES/SendGrid/Mailgun/Postmark/Mailjet, HTML template parsing with variable substitution, attachment handling, and header injection prevention.

Maintained for you: when a provider changes or retires an API, the transport gets updated; your code doesn't.

What Pro provides:

  • Drop-in support for 6+ email providers
  • HTML templates with {{ variable }} syntax and conditionals
  • Fluent attachment API with content streaming
  • Automatic header injection prevention

Installation

Flick Pro requires a license. See Pro installation for setup instructions.

Configuration

Mail requires a fromAddress and mailer transport configuration:

$config = [
    'services' => [
        'mail' => [
            'fromAddress' => 'noreply@example.com',
            'fromName' => 'My App',  // Optional
            'mailer' => [
                'transport' => 'smtp',
                'host' => 'smtp.example.com',
                'port' => 587,
                'encryption' => 'tls',
                'username' => 'your-username',
                'password' => 'your-password'
            ]
        ]
    ]
];

$form = new Flick($config);

Supported Transports

PHP mail()

The same 'mail' transport Flick's free core ships with, so an existing core config keeps working when you upgrade. Delivers through the server's sendmail — no credentials needed.

'mailer' => [
    'transport' => 'mail'
]

SMTP

'mailer' => [
    'transport' => 'smtp',
    'host' => 'smtp.example.com',
    'port' => 587,
    'encryption' => 'tls',
    'username' => 'your-username',
    'password' => 'your-password'
]

encryption defaults to 'tls' and accepts:

Value Behavior
'tls' or 'starttls' STARTTLS, required — the send fails rather than quietly falling back to plaintext
'ssl' Implicit TLS from the first byte (smtps://), usually port 465
'none' or '' No encryption at all. Only for a local mail catcher — never over a network

Any other value throws an InvalidArgumentException when the mailer is built, so a typo fails loudly instead of quietly sending in the clear. Leaving encryption out entirely — or setting it to null — gives you the 'tls' default.

If sending fails with "TLS required but neither TLS or STARTTLS are in use", your server isn't offering STARTTLS. Use 'ssl' for a port-465 server, or 'none' for a local test server that doesn't do TLS.

Amazon SES

'mailer' => [
    'transport' => 'ses',
    'accessKey' => 'your-ses-access-key',
    'secretKey' => 'your-ses-secret-key',
    'region' => 'us-east-1'
]

SendGrid

'mailer' => [
    'transport' => 'sendgrid',
    'key' => 'your-sendgrid-api-key'
]

Mailgun

'mailer' => [
    'transport' => 'mailgun',
    'key' => 'your-mailgun-api-key',
    'domain' => 'mg.example.com',
    'region' => 'us'
]

Postmark

'mailer' => [
    'transport' => 'postmark',
    'token' => 'your-postmark-server-token'
]

Mailjet

'mailer' => [
    'transport' => 'mailjet',
    'username' => 'your-mailjet-api-key',
    'password' => 'your-mailjet-secret-key'
]

Mailtrap (Testing)

'mailer' => [
    'transport' => 'mailtrap',
    'host' => 'sandbox.smtp.mailtrap.io',
    'port' => 2525,
    'username' => 'your-mailtrap-username',
    'password' => 'your-mailtrap-password'
]

Basic Usage

Send a Simple Email

if ($form->mail->send('user@example.com', 'Hello!', 'This is the message body.')) {
    $form->successMessage('Email sent!');
} else {
    $form->errorMessage('Failed to send email.');
}

With Options

$form->mail->send('user@example.com', 'Hello!', 'Message body', [
    'fromAddress' => 'custom@example.com',
    'fromName' => 'Custom Sender',
    'cc' => 'copy@example.com',
    'bcc' => 'hidden@example.com',
    'replyTo' => 'reply@example.com',
    'priority' => true  // Mark as high priority
]);

Send Form Data

Automatically format and send form submission data:

if ($form->submitted()) {
    $request = $form->request('Name[required], Email[required, email], Message[required]');

    if ($form->ok()) {
        $form->mail->sendFormData('admin@example.com', 'New Contact Form', $request);
    }
}

This sends a plain-text body like the following, plus an HTML version with the same fields laid out in a table:

Name: John Doe
Email: john@example.com
Message: Hello, I have a question...

Templates

Template Syntax

Create an HTML email template file:

<!-- views/email.html -->
<html>
<body>
    <h1>Hello, {{ name }}!</h1>

    <p>Thank you for your order #{{ order.id }}.</p>

    @if(premium)
        <p>As a premium member, you get free shipping!</p>
    @endif

    <p>Items ordered:</p>
    <ul>
        @if(items)
            <li>{{ items }}</li>
        @endif
    </ul>

    <!-- Raw HTML (unescaped) -->
    {{{ signature }}}
</body>
</html>

Variable Syntax

Syntax Description
{{ variable }} Output with HTML escaping
{{{ variable }}} Raw output (no escaping)
{{ user.name }} Nested data with dot notation
@if(variable) ... @endif Conditional block

Using Templates

Configure the template path and send with data:

$config = [
    'services' => [
        'mail' => [
            'fromAddress' => 'noreply@example.com',
            'view' => '/path/to/views/email.html',
            'mailer' => [...]
        ]
    ]
];

$form->mail->send('user@example.com', 'Your Order', 'Plain text fallback', [
    'data' => [
        'name' => 'John',
        'order' => ['id' => '12345'],
        'premium' => true,
        'items' => 'Widget, Gadget',
        'signature' => '<p><strong>The Team</strong></p>'
    ]
]);

Two things to know about the configured view:

  • It only renders when the send passes a data option — without data, the view is skipped and the plain-text body goes out alone.
  • When it renders, it becomes the HTML body and any inline 'html' option on that send is ignored.
// No 'data' here, so the configured view is NOT used — plain text only
$form->mail->send('user@example.com', 'Hi', 'Plain text body');

Attachments

Fluent API

$form->mail
    ->attach('/path/to/document.pdf')
    ->attach('/path/to/image.jpg', 'custom-name.jpg')
    ->send('user@example.com', 'Files Attached', 'See attachments.');

Attach Raw Content

$csvContent = "Name,Email\nJohn,john@example.com";

$form->mail
    ->attachContent($csvContent, 'users.csv', 'text/csv')
    ->send('admin@example.com', 'User Export', 'Attached is the export.');

Via Options Array

$form->mail->send('user@example.com', 'Subject', 'Message', [
    'attachments' => [
        '/path/to/file.pdf',
        ['path' => '/path/to/image.jpg', 'name' => 'photo.jpg'],
        ['content' => $pdfContent, 'name' => 'report.pdf', 'contentType' => 'application/pdf']
    ]
]);

Clear Attachments

$form->mail->clearAttachments();

Methods

send()

Send an email message.

$form->mail->send(
    string|array $to,
    string $subject,
    string $body,
    array $options = []
): bool
Parameter Type Description
$to string|array Recipient email address(es)
$subject string Email subject line
$body string Email body (text version)
$options array Additional options (see below)

Options:

Option Type Description
fromAddress string Override sender address
fromName string Override sender name
cc string|array Carbon copy recipient(s)
bcc string|array Blind carbon copy recipient(s)
replyTo string Reply-to address
priority bool Mark as high priority
data array Template data for view processing
html string HTML version of email
attachments array Array of attachments

Returns: true on success, false on failure.

sendFormData()

Send formatted form data as an email.

$form->mail->sendFormData(
    string|array $to,
    string $subject,
    array $formData,
    array $options = []
): bool
Parameter Type Description
$to string|array Recipient email address(es)
$subject string Email subject line
$formData array Form field data to send
$options array Same options as send(), plus exclude — an array of field names to leave out of the email

attach()

Attach a file to the next email.

$form->mail->attach(
    string $path,
    ?string $name = null
): self
Parameter Type Description
$path string File path to attach
$name string|null Custom filename (defaults to basename)

Returns: self for fluent chaining.

attachContent()

Attach raw content as a file.

$form->mail->attachContent(
    string $content,
    string $name,
    string $mimeType
): self

clearAttachments()

Clear all pending attachments.

$form->mail->clearAttachments(): self

Complete Example

<?php
require 'vendor/autoload.php';

$form = new Flick\Flick([
    'services' => [
        'mail' => [
            'fromAddress' => 'noreply@myapp.com',
            'fromName' => 'My App',
            'view' => __DIR__ . '/views/contact-notification.html',
            'mailer' => [
                'transport' => 'sendgrid',
                'key' => $_ENV['SENDGRID_API_KEY']
            ]
        ]
    ]
]);

$form->create('Name, Email, Phone, Message|textarea');

if ($form->submitted()) {
    $request = $form->request('
        Name[required, min:2],
        Email[required, email],
        Phone[phone],
        Message[required, min:10]
    ');

    if ($form->ok()) {
        // Send notification to admin with template
        $sent = $form->mail->send(
            'admin@myapp.com',
            'New Contact Form Submission',
            'New contact form submission received.',
            [
                'data' => [
                    'name' => $request['name'],
                    'email' => $request['email'],
                    'phone' => $request['phone'] ?: 'Not provided',
                    'message' => $request['message'],
                    'submitted_at' => date('F j, Y g:i A')
                ]
            ]
        );

        // Send confirmation to user
        $form->mail->send(
            $request['email'],
            'We received your message',
            "Hi {$request['name']},\n\nThank you for contacting us. We'll get back to you soon!"
        );

        if ($sent) {
            $form->successMessage('Message sent successfully!');
        }
    }
}

Security Features

  • Header Injection Prevention: All email addresses are validated against injection patterns
  • Attachment Path Containment: Set 'attachmentBasePath' => '/var/app/files' in the mail config and every path attachment must resolve inside that directory — a path that escapes it (e.g. ../../etc/passwd) fails the send with an error instead of mailing out server files
  • HTML Escaping: Template variables are escaped by default (double curly braces) to prevent XSS