Mail (SMTP)
The starter ships one real, sendable job:
AppJobs::SendTestMail. Its handle()
(app/jobs/SendTestMail.cpp:58) opens an SMTP session with
libcurl, builds an RFC 822 message with an HTML body, authenticates, sends,
and logs the result. Everything is driven by the MAIL_* block in
.env.
MAIL_* configuration
MAIL_DRIVER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=465
MAIL_USERNAME="user@example.com"
MAIL_PASSWORD="app-password"
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="user@example.com"
MAIL_FROM_NAME="Your App"
MAIL_AUTHENTICATION=plain
| Key | Purpose |
|---|---|
MAIL_HOST, MAIL_PORT | SMTP endpoint. |
MAIL_USERNAME, MAIL_PASSWORD | Login credentials (libcurl auto-negotiates AUTH mechanism). |
MAIL_ENCRYPTION | tls, ssl, or smtps. ssl/smtps force implicit TLS regardless of port. |
MAIL_FROM_ADDRESS, MAIL_FROM_NAME | Envelope sender + From: header display name. |
MAIL_AUTHENTICATION | Informational; libcurl auto-negotiates. |
Values may be quoted with "..."; the job strips surrounding
quotes before use.
Ports & encryption
- 465 → implicit TLS (
smtps://). Handshake happens before SMTP banner. - 587 → STARTTLS. The job uses
smtp://and forces TLS viaCURLUSESSL_ALL. - Any other port with
MAIL_ENCRYPTION=sslorsmtps→ forced implicit TLS.
HTML content
The RFC 822 message header is fixed to
Content-Type: text/html; charset=UTF-8 with 8-bit CTE, so the
body field can contain arbitrary HTML including UTF-8 text:
curl "http://localhost:9090/api/jobs/send-mail?to=you@x.com&subject=Hi&body=<h1>Hello</h1><p><b>Bold</b></p>"
Debugging the SMTP dialog
If a send succeeds according to libcurl but never lands in the inbox (spam, greylist, silent rejection), flip verbose mode on temporarily:
// app/jobs/SendTestMail.cpp
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); // change 0L to 1L
Rebuild, restart, retry. You get the full TLS handshake plus the SMTP
dialog on the server's stdout — EHLO, AUTH,
MAIL FROM, RCPT TO, DATA, and the
final 250 2.0.0 Ok: queued as ....
Verbose SMTP logs leak sensitive headers and can hurt performance.
Flip back to 0L before committing.
Provider examples
Gmail (app password, port 465):
MAIL_HOST=smtp.gmail.com
MAIL_PORT=465
MAIL_USERNAME=you@gmail.com
MAIL_PASSWORD=xxxx-xxxx-xxxx-xxxx # app password, not your account password
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS=you@gmail.com
SendGrid (API relay, port 587 STARTTLS):
MAIL_HOST=smtp.sendgrid.net
MAIL_PORT=587
MAIL_USERNAME=apikey
MAIL_PASSWORD=SG.xxxxxxxxxxxxxxxxxxxxxxxx
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=you@yourdomain.com
Custom SMTP (port 465):
MAIL_HOST=mail.your-host.com
MAIL_PORT=465
MAIL_USERNAME=info@yourdomain.com
MAIL_PASSWORD=your-mailbox-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=info@yourdomain.com
MAIL_FROM_NAME="Your Brand"
See Jobs for the job pipeline itself, and Events & Jobs walkthrough for an end-to-end example.