Skip to content

Commit 1e496e7

Browse files
committed
docs(mailer): align USAGE with SMTP behavior and examples 📖
- Add attachment encoding behavior notes and rename example content variable to rawFileContent - Add Combining Body Modes section, evaluation order, and TOC fixes (Send Result link, Advanced Features order) - Add custom header rules and reserved names list - Add DKIM headerFieldNames configuration row and optional signed-header prose - Add STARTTLS upgrade flow, port 587 requirement, and TLS troubleshooting note - Adjust README transfer encodings feature bullet - Align Error Handling and Debug Snippet with safe error text, regex checks, and wrapped-error note - Rephrase Send Result without "now" and tighten Message Properties encoding paragraph
1 parent 47d1a2a commit 1e496e7

2 files changed

Lines changed: 53 additions & 13 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Lightweight Deno SMTP mailer with flexible configuration and formatting.
1616
- **Flexible recipients**: supports string, object, and mixed recipient formats
1717
- **Rich message content**: plain text, HTML, mixed body, and custom headers
1818
- **Attachments and inline media**: supports file attachments and embedded images
19-
- **Transfer control options**: supports base64, 7bit, and quoted-printable encodings
19+
- **Transfer encodings**: base64, 7bit, quoted-printable for attachment content
2020
- **Calendar invitations**: generates ICS calendar payload for meeting invites
2121
- **Structured send result**: returns message id, envelope, accepted, rejected, and response
2222
- **Zero external runtime deps**: built with Deno native capabilities

USAGE.md

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ This document explains the full **Deno Mailer** usage flow, from SMTP configurat
1818
- [Multiple Recipients](#multiple-recipients)
1919
- [Advanced Recipient Formats](#advanced-recipient-formats)
2020
- [Advanced Features](#advanced-features)
21+
- [Combining Body Modes](#combining-body-modes)
2122
- [File Attachments](#file-attachments)
2223
- [Attachment Encoding Options](#attachment-encoding-options)
2324
- [Embedded Images](#embedded-images)
@@ -28,6 +29,7 @@ This document explains the full **Deno Mailer** usage flow, from SMTP configurat
2829
- [Main API](#main-api)
2930
- [Configuration Options](#configuration-options)
3031
- [Message Properties](#message-properties)
32+
- [Send Result](#send-result)
3133
- [Error Handling](#error-handling)
3234
- [Provider Examples](#provider-examples)
3335
- [Gmail SMTP](#gmail-smtp)
@@ -106,6 +108,8 @@ const transporter = mailer.transporter({
106108
})
107109
```
108110

111+
Optional `dkim.headerFieldNames` lists which message headers are included in the DKIM `h=` tag (use lowercase names). If you omit it, the default set is `from`, `to`, `subject`, `date`, `message-id`, `mime-version`, `content-type`.
112+
109113
### Pooling and Reuse
110114

111115
```ts
@@ -129,15 +133,22 @@ const transporter = mailer.transporter({
129133

130134
### TLS and SSL Options
131135

136+
With `secure: false`, the client opens a plain TCP connection, sends `EHLO`, then:
137+
138+
- If the server advertises `STARTTLS`, the client upgrades to TLS and sends `EHLO` again (this applies to any port where the server offers `STARTTLS`, not only 587).
139+
- On **port 587**, the server **must** advertise `STARTTLS`. If it does not, the client throws an error.
140+
141+
With `secure: true`, the connection uses TLS from the first byte (typical for port 465).
142+
132143
```ts
133-
// Port 587 with STARTTLS upgrade.
144+
// Port 587: plain connect, then mandatory STARTTLS upgrade after EHLO.
134145
{
135146
host: 'smtp.gmail.com',
136147
port: 587,
137148
secure: false
138149
}
139150

140-
// Port 465 with direct TLS.
151+
// Port 465: TLS from connect.
141152
{
142153
host: 'smtp.gmail.com',
143154
port: 465,
@@ -221,6 +232,18 @@ await transporter.send({
221232

222233
## Advanced Features
223234

235+
### Combining Body Modes
236+
237+
The formatter builds **one** MIME structure per message. It does **not** combine embedded images, calendar parts, and file attachments into a single custom multipart tree. Evaluation order is:
238+
239+
1. `embeddedImages` → multipart/related (with HTML and related parts)
240+
2. Else `calendarEvent` → multipart/alternative including the calendar
241+
3. Else `attachments` → multipart/mixed
242+
4. Else `html` and `text` together → multipart/alternative
243+
5. Else `html` only, or plain `text` only
244+
245+
If you set more than one of `embeddedImages`, `calendarEvent`, and `attachments`, the earlier branch wins and the others are ignored for structure (for example calendar plus attachments in one send is not supported as one merged layout).
246+
224247
### File Attachments
225248

226249
```ts
@@ -243,6 +266,12 @@ await transporter.send({
243266

244267
### Attachment Encoding Options
245268

269+
Transfer encoding behavior:
270+
271+
- **`base64`:** Pass **raw** content (`string` as UTF-8 text, or `Uint8Array` as bytes). The library encodes to Base64. Do **not** pass a string that is already Base64 unless you want it encoded again (wrong for binary files).
272+
- **`7bit`:** Content must be **ASCII only** (bytes 0–127). Non-ASCII input throws.
273+
- **`quoted-printable`:** Bytes are escaped per quoted-printable rules. The encoder does **not** fold lines to ~76 characters, which some strict MTAs expect for long lines.
274+
246275
```ts
247276
// Use base64, 7bit, or quoted-printable transfer encoding.
248277
await transporter.send({
@@ -253,7 +282,7 @@ await transporter.send({
253282
attachments: [
254283
{
255284
filename: 'base64.txt',
256-
content: fileBase64Content,
285+
content: rawFileContent,
257286
contentType: 'text/plain',
258287
encoding: 'base64'
259288
},
@@ -414,6 +443,12 @@ await transporter.send({
414443
})
415444
```
416445

446+
Rules:
447+
448+
- Header **names** must match token characters (see RFC 5322 `atext`-style set used in code). Empty names are rejected.
449+
- Names and values must **not** contain CR or LF.
450+
- These names are **reserved** and cannot be set via `headers` (the library owns them): `bcc`, `cc`, `content-disposition`, `content-id`, `content-transfer-encoding`, `content-type`, `date`, `from`, `message-id`, `mime-version`, `reply-to`, `subject`, `to`.
451+
417452
## API Reference
418453

419454
### Main API
@@ -441,6 +476,7 @@ await transporter.send({
441476
| `dkim.domainName` | string | no | DKIM signing domain | `'example.com'` |
442477
| `dkim.keySelector` | string | no | DKIM DNS selector | `'mail'` |
443478
| `dkim.privateKey` | string | no | PEM private signing key | `'-----BEGIN PRIVATE KEY-----...'` |
479+
| `dkim.headerFieldNames` | string[] | no | Headers in DKIM `h=` list | `['from','to','subject',...]` |
444480

445481
### Message Properties
446482

@@ -459,12 +495,11 @@ await transporter.send({
459495
| `calendarEvent` | object | no | Calendar invitation |
460496
| `headers` | object | no | Custom email headers |
461497

462-
Attachment and embedded image encoding supports `base64`, `7bit`, and `quoted-printable`.
463-
Embedded image disposition supports `inline` and `attachment`.
498+
Attachment and embedded image `encoding` supports `base64` (library encodes raw content), `7bit` (ASCII only), and `quoted-printable` (no automatic line folding). Embedded image `disposition` supports `inline` and `attachment`.
464499

465500
### Send Result
466501

467-
`transporter.send()` now resolves a structured result:
502+
`transporter.send()` resolves a structured result:
468503

469504
- `messageId`: Message-ID header value
470505
- `envelope`: SMTP envelope sender and recipients
@@ -474,16 +509,19 @@ Embedded image disposition supports `inline` and `attachment`.
474509

475510
### Error Handling
476511

512+
Many failures surface as `Error` messages prefixed with `Failed to send message:` or `SMTP connection failed:` plus the underlying reason. Simple `message.includes('…')` checks can miss cases if the wording changes, so prefer logging the full message or matching a broader substring.
513+
477514
```ts
478515
// Catch transport or SMTP response errors.
479516
try {
480517
await transporter.send(message)
481518
} catch (error) {
482-
console.error('Email failed:', error.message)
483-
if (error.message.includes('authentication')) {
519+
const text = error instanceof Error ? error.message : String(error)
520+
console.error('Email failed:', text)
521+
if (/authentication|auth/i.test(text)) {
484522
// Handle auth error.
485-
} else if (error.message.includes('connection')) {
486-
// Handle connectivity error.
523+
} else if (/connection|connect|STARTTLS|TLS/i.test(text)) {
524+
// Handle connectivity or TLS error.
487525
}
488526
}
489527
```
@@ -554,7 +592,8 @@ const transporter = mailer.transporter({
554592

555593
### TLS Errors
556594

557-
- Use `secure: false` for `587` (STARTTLS)
595+
- On port `587`, the server must advertise `STARTTLS` or the client will error
596+
- Use `secure: false` for `587` (STARTTLS after EHLO)
558597
- Use `secure: true` for `465` (direct TLS)
559598
- Verify the server certificate chain
560599

@@ -566,6 +605,7 @@ try {
566605
await transporter.send(message)
567606
console.log('Email sent successfully')
568607
} catch (error) {
569-
console.error('SMTP Error:', error.message)
608+
const text = error instanceof Error ? error.message : String(error)
609+
console.error('SMTP Error:', text)
570610
}
571611
```

0 commit comments

Comments
 (0)