Skip to content

Commit 9659fed

Browse files
committed
Harden send_mail API and TLS configuration
1 parent 77797c9 commit 9659fed

11 files changed

Lines changed: 246 additions & 243 deletions

File tree

Cargo.lock

Lines changed: 6 additions & 83 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,14 @@ openidconnect = { version = "4.0.0", default-features = false, features = ["acce
7979
encoding_rs = "0.8.35"
8080
odbc-sys = { version = "0", optional = true }
8181
regex = "1"
82-
lettre = { version = "0.11", default-features = false, features = ["builder", "smtp-transport", "tokio1", "tokio1-native-tls"] }
82+
lettre = { version = "0.11", default-features = false, features = [
83+
"aws-lc-rs",
84+
"builder",
85+
"rustls-native-certs",
86+
"smtp-transport",
87+
"tokio1-rustls",
88+
"webpki-roots",
89+
] }
8390

8491
# OpenTelemetry / tracing
8592
tracing = "0.1"

configuration.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ Here are the available configuration options and their default values:
4141
| `environment` | development | The environment in which SQLPage is running. Can be either `development` or `production`. In `production` mode, SQLPage will hide error messages and stack traces from the user, and will cache sql files in memory to avoid reloading them from disk. |
4242
| `cache_stale_duration_ms` | 1000 (prod), 0 (dev) | The duration in milliseconds that a file can be cached before its freshness is checked against the filesystem. Defaults to 1000ms (1 second) in production and 0ms in development. |
4343
| `content_security_policy` | `script-src 'self' 'nonce-{NONCE}'` | The [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) to set in the HTTP headers. If you get CSP errors in the browser console, you can set this to the empty string to disable CSP. If you want a custom CSP that contains a nonce, include the `'nonce-{NONCE}'` directive in your configuration string and it will be populated with a random value per request. |
44-
| `smtp_host` | | SMTP server used by the `sqlpage.send_mail` function. Accepts only a host name or `host:port`; if no port is provided, SQLPage uses port 25. Set with `SMTP_HOST` in the environment. |
44+
| `smtp_host` | | SMTP server host used by the `sqlpage.send_mail` function. Set with `SMTP_HOST` in the environment. |
45+
| `smtp_port` | 25 (`none`), 465 (`tls`), or 587 (`starttls`) | SMTP server port. The default depends on `smtp_tls_mode`. Set this explicitly for relays using a nonstandard port. |
4546
| `smtp_username` | | Optional SMTP user name for `sqlpage.send_mail`. When set, SQLPage authenticates to `SMTP_HOST` using this user name and `smtp_password`. Credentials require `smtp_tls_mode` to be `starttls` or `tls`. |
46-
| `smtp_password` | | Optional SMTP password for `sqlpage.send_mail` when `smtp_username` is set. |
47+
| `smtp_password` | | Optional SMTP password for `sqlpage.send_mail`. `smtp_username` and `smtp_password` must be configured together. |
48+
| `smtp_from` | | Default sender address for `sqlpage.send_mail`, optionally including a display name. Individual messages can override it with their `from` property. |
4749
| `smtp_tls_mode` | `starttls` | Encryption mode for `sqlpage.send_mail`: `starttls` requires a STARTTLS upgrade, `tls` uses TLS from connection start, and `none` permits plaintext only without credentials for trusted local SMTP servers. |
4850
| `system_root_ca_certificates` | false | Whether to use the system root CA certificates to validate SSL certificates when making http requests with `sqlpage.fetch`. If set to false, SQLPage will use its own set of root CA certificates. If the `SSL_CERT_FILE` or `SSL_CERT_DIR` environment variables are set, they will be used instead of the system root CA certificates. |
4951
| `max_recursion_depth` | 10 | Maximum depth of recursion allowed in the `run_sql` function. Maximum value is 255. |

examples/official-site/sqlpage/migrations/75_send_mail.sql

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,39 @@ INSERT INTO sqlpage_functions (
22
"name",
33
"introduced_in_version",
44
"icon",
5-
"description_md",
6-
"return_type"
5+
"description_md"
76
)
87
VALUES (
98
'send_mail',
109
'0.45.0',
1110
'mail',
1211
'Sends an email using the SMTP server configured with `SMTP_HOST`.
1312
14-
`SMTP_HOST` must contain only a host name or `host:port`; URL schemes and paths are rejected. When no port is specified, SQLPage uses port 25.
13+
`SMTP_HOST` contains the relay host name. Set `SMTP_PORT` when the relay does not use the default for the selected encryption mode: 587 for `starttls`, 465 for `tls`, or 25 for `none`.
1514
1615
`SMTP_TLS_MODE` defaults to `starttls`, which requires a STARTTLS upgrade before sending email or credentials. Set it to `tls` for implicit TLS, commonly used on port 465. Plaintext mode (`none`) is allowed only without credentials and should be used only for trusted local SMTP servers.
1716
1817
If your SMTP server requires authentication, configure `SMTP_USERNAME` and `SMTP_PASSWORD` as well.
1918
2019
The function accepts a single JSON object argument. The required properties are:
2120
22-
- `recipient`: email address to send to, optionally including a display name such as `"Jane Doe <jane@example.com>"`.
21+
- `to`: email address to send to, optionally including a display name such as `"Jane Doe <jane@example.com>"`.
2322
- `subject`: email subject.
2423
- `body`: plain text email body.
2524
2625
Optional properties:
2726
28-
- `sender`: sender address. Defaults to `SQLPage <sqlpage@localhost>`.
27+
- `from`: sender address. It may be omitted when `SMTP_FROM` configures a default sender.
2928
- `reply_to`: reply-to address.
3029
31-
After the SMTP server accepts the message, the function returns its JSON argument unchanged. It returns `NULL` when passed `NULL`, and raises an error if the message cannot be sent.
30+
The function returns `NULL` after the SMTP relay accepts the message and raises an error if the message cannot be sent. The argument is required; passing `NULL` is an error.
3231
3332
### Example
3433
3534
```sql
3635
set message = json_object(
37-
''recipient'', ''admin@example.com'',
38-
''sender'', ''contact@example.com'',
36+
''to'', ''admin@example.com'',
37+
''from'', ''contact@example.com'',
3938
''subject'', ''New contact form message'',
4039
''body'', ''Hello from SQLPage!''
4140
);
@@ -50,16 +49,15 @@ select ''email'' as name, ''email'' as type, true as required;
5049
select ''message'' as name, ''textarea'' as type, true as required;
5150
5251
set mail = json_object(
53-
''recipient'', ''admin@example.com'',
52+
''to'', ''admin@example.com'',
5453
''reply_to'', $email,
5554
''subject'', ''Website contact form'',
5655
''body'', $message
5756
);
5857
select sqlpage.send_mail($mail)
5958
where $message is not null;
6059
```
61-
',
62-
'JSON'
60+
'
6361
);
6462

6563
INSERT INTO sqlpage_function_parameters (
@@ -73,6 +71,6 @@ VALUES (
7371
'send_mail',
7472
1,
7573
'message',
76-
'A JSON object containing the email to send. Required properties are `recipient`, `subject`, and `body`. Optional properties are `sender` and `reply_to`.',
74+
'A JSON object containing the email to send. Required properties are `to`, `subject`, and `body`. Optional properties are `from` (required unless `SMTP_FROM` is configured) and `reply_to`. Unknown properties are rejected to catch misspellings.',
7775
'JSON'
7876
);

examples/sending emails/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,22 @@ docker compose up
1010

1111
Open http://localhost:8080 to send an email, then inspect it in the Mailpit inbox at http://localhost:8025.
1212

13-
The SMTP server is configured in [`docker-compose.yml`](./docker-compose.yml) with `SMTP_HOST=mailpit:1025` and `SMTP_TLS_MODE=none`. Plaintext mode is intended only for trusted local SMTP servers such as Mailpit.
13+
The SMTP server is configured in [`docker-compose.yml`](./docker-compose.yml) with `SMTP_HOST=mailpit`, `SMTP_PORT=1025`, and `SMTP_TLS_MODE=none`. Plaintext mode is intended only for trusted local SMTP servers such as Mailpit.
1414

1515
For a remote SMTP relay, keep the default `SMTP_TLS_MODE=starttls`, or set it to `tls` when the relay requires implicit TLS. Configure `SMTP_USERNAME` and `SMTP_PASSWORD` when authentication is required; SQLPage rejects credentials in plaintext mode.
1616

1717
The form handler sends the message with a single function call:
1818

1919
```sql
2020
set message = json_object(
21-
'recipient', :recipient,
22-
'sender', :sender,
21+
'to', :recipient,
22+
'from', :sender,
2323
'subject', :subject,
2424
'body', :body
2525
);
26-
set sent_message = sqlpage.send_mail($message);
26+
set _ = sqlpage.send_mail($message);
2727
```
2828

29-
After the SMTP server accepts the email, `sqlpage.send_mail` returns the message JSON unchanged. It raises an error when delivery to the SMTP server fails.
29+
`sqlpage.send_mail` returns `NULL` after the SMTP relay accepts the message. It raises an error when the relay rejects the message or cannot be reached, so statements after the call run only on success.
3030

3131
Do not expose an unrestricted form like this publicly. In production, authenticate users, restrict recipients, validate input, and add rate limiting to prevent abuse.

examples/sending emails/docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ services:
44
ports:
55
- "8080:8080"
66
environment:
7-
SMTP_HOST: mailpit:1025
7+
SMTP_HOST: mailpit
8+
SMTP_PORT: 1025
89
SMTP_TLS_MODE: none
910
volumes:
1011
- .:/var/www

examples/sending emails/email.sql

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
set message = json_object(
2-
'recipient', :recipient,
3-
'sender', :sender,
2+
'to', :recipient,
3+
'from', :sender,
44
'subject', :subject,
55
'body', :body
66
);
@@ -9,8 +9,7 @@ set sent_message = sqlpage.send_mail($message);
99
select
1010
'alert' as component,
1111
'success' as color,
12-
'Email sent successfully' as title
13-
where $sent_message is not null;
12+
'Email sent successfully' as title;
1413

1514
select 'button' as component;
1615
select 'Send another email' as title, 'index.sql' as link;

0 commit comments

Comments
 (0)