Tested and maintained, so you don't have to. $99/year.
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
dataoption — withoutdata, 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