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
KeyPurpose
MAIL_HOST, MAIL_PORTSMTP endpoint.
MAIL_USERNAME, MAIL_PASSWORDLogin credentials (libcurl auto-negotiates AUTH mechanism).
MAIL_ENCRYPTIONtls, ssl, or smtps. ssl/smtps force implicit TLS regardless of port.
MAIL_FROM_ADDRESS, MAIL_FROM_NAMEEnvelope sender + From: header display name.
MAIL_AUTHENTICATIONInformational; 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 via CURLUSESSL_ALL.
  • Any other port with MAIL_ENCRYPTION=ssl or smtps → 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 ....

Do not commit the change

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.