@webamoki/web-svelte 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Email Utility Package
|
|
2
|
+
|
|
3
|
+
This package provides utilities for sending emails using AWS Simple Email Service (SES).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
The AWS SES SDK is already included as a dependency. You'll need to configure your AWS credentials and region.
|
|
8
|
+
|
|
9
|
+
## Configuration
|
|
10
|
+
|
|
11
|
+
### Environment Variables
|
|
12
|
+
|
|
13
|
+
The email utility requires the following environment variables:
|
|
14
|
+
|
|
15
|
+
- `AWS_REGION` - AWS region where SES is configured (e.g., `us-east-1`)
|
|
16
|
+
- `AWS_ACCESS_KEY_ID` - AWS access key ID
|
|
17
|
+
- `AWS_SECRET_ACCESS_KEY` - AWS secret access key
|
|
18
|
+
|
|
19
|
+
### SES Setup
|
|
20
|
+
|
|
21
|
+
Before using this utility, ensure:
|
|
22
|
+
|
|
23
|
+
1. Your AWS account has SES enabled in your chosen region
|
|
24
|
+
2. Your sender email address(es) are verified in SES
|
|
25
|
+
3. If in SES sandbox mode, recipient addresses must also be verified
|
|
26
|
+
4. Your account has the necessary IAM permissions to send emails via SES
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### Basic Example
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { sendEmail } from '@webamoki/web-svelte/utils/email';
|
|
34
|
+
|
|
35
|
+
// Send a simple text email
|
|
36
|
+
const messageId = await sendEmail({
|
|
37
|
+
to: 'recipient@example.com',
|
|
38
|
+
subject: 'Hello from SES',
|
|
39
|
+
text: 'This is a plain text email.',
|
|
40
|
+
from: 'sender@example.com'
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log('Email sent with message ID:', messageId);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### HTML Email
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
await sendEmail({
|
|
50
|
+
to: 'recipient@example.com',
|
|
51
|
+
subject: 'Welcome!',
|
|
52
|
+
html: '<h1>Welcome to our service!</h1><p>Thanks for signing up.</p>',
|
|
53
|
+
text: 'Welcome to our service! Thanks for signing up.', // Fallback for email clients that don't support HTML
|
|
54
|
+
from: 'noreply@example.com',
|
|
55
|
+
fromName: 'My Application'
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Multiple Recipients
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
await sendEmail({
|
|
63
|
+
to: ['user1@example.com', 'user2@example.com'],
|
|
64
|
+
cc: 'manager@example.com',
|
|
65
|
+
bcc: ['archive@example.com', 'backup@example.com'],
|
|
66
|
+
subject: 'Team Update',
|
|
67
|
+
text: 'This is a team-wide announcement.',
|
|
68
|
+
from: 'team@example.com'
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### With Reply-To
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
await sendEmail({
|
|
76
|
+
to: 'customer@example.com',
|
|
77
|
+
subject: 'Your order confirmation',
|
|
78
|
+
html: '<p>Your order has been confirmed!</p>',
|
|
79
|
+
from: 'noreply@example.com',
|
|
80
|
+
fromName: 'Order System',
|
|
81
|
+
replyTo: 'support@example.com'
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## API Reference
|
|
86
|
+
|
|
87
|
+
### `sendEmail(options: SendEmailOptions): Promise<string>`
|
|
88
|
+
|
|
89
|
+
Sends an email via AWS SES.
|
|
90
|
+
|
|
91
|
+
**Returns:** Promise that resolves to the SES message ID (string) on success.
|
|
92
|
+
|
|
93
|
+
**Throws:** Error with descriptive message if:
|
|
94
|
+
|
|
95
|
+
- Validation fails (missing required fields, invalid recipients, etc.)
|
|
96
|
+
- AWS SES returns an error (credentials, permissions, service issues)
|
|
97
|
+
- SES response does not contain a MessageId (unlikely but handled explicitly)
|
|
98
|
+
|
|
99
|
+
### `SendEmailOptions`
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
interface SendEmailOptions {
|
|
103
|
+
to: string | string[]; // Required: recipient email address(es)
|
|
104
|
+
cc?: string | string[]; // Optional: CC recipients
|
|
105
|
+
bcc?: string | string[]; // Optional: BCC recipients
|
|
106
|
+
subject: string; // Required: email subject
|
|
107
|
+
text?: string; // Optional: plain text body
|
|
108
|
+
html?: string; // Optional: HTML body
|
|
109
|
+
from: string; // Required: sender email
|
|
110
|
+
fromName?: string; // Optional: sender display name
|
|
111
|
+
replyTo?: string | string[]; // Optional: reply-to address(es)
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Error Handling
|
|
116
|
+
|
|
117
|
+
The `sendEmail` function validates inputs and will throw errors for:
|
|
118
|
+
|
|
119
|
+
- Missing required fields (`to`, `from`, `subject`, at least one of `text` or `html`)
|
|
120
|
+
- Empty recipient list (no valid email addresses after filtering)
|
|
121
|
+
- AWS SES errors (credentials, permissions, service issues)
|
|
122
|
+
|
|
123
|
+
**Note:** The function automatically filters out empty strings and whitespace-only values from recipient arrays (`to`, `cc`, `bcc`, `replyTo`). For example, `['', 'valid@example.com', ' ']` will be processed as `['valid@example.com']`.
|
|
124
|
+
|
|
125
|
+
Always wrap calls in try-catch:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
try {
|
|
129
|
+
const messageId = await sendEmail({
|
|
130
|
+
to: 'user@example.com',
|
|
131
|
+
subject: 'Test',
|
|
132
|
+
text: 'Hello!',
|
|
133
|
+
from: 'sender@example.com'
|
|
134
|
+
});
|
|
135
|
+
console.log('Success:', messageId);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error('Failed to send email:', error.message);
|
|
138
|
+
// Handle error appropriately
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## SvelteKit Integration
|
|
143
|
+
|
|
144
|
+
### Server-Only Usage
|
|
145
|
+
|
|
146
|
+
⚠️ **Important:** This utility should only be used in server-side code (e.g., `+page.server.ts`, `+server.ts`, or server-side functions) as it requires AWS credentials.
|
|
147
|
+
|
|
148
|
+
### Example: Contact Form Handler
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// src/routes/contact/+page.server.ts
|
|
152
|
+
import { sendEmail } from '@webamoki/web-svelte/utils/email';
|
|
153
|
+
import type { Actions } from './$types';
|
|
154
|
+
|
|
155
|
+
export const actions: Actions = {
|
|
156
|
+
default: async ({ request }) => {
|
|
157
|
+
const formData = await request.formData();
|
|
158
|
+
const email = formData.get('email') as string;
|
|
159
|
+
const message = formData.get('message') as string;
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
await sendEmail({
|
|
163
|
+
to: 'admin@example.com',
|
|
164
|
+
subject: `Contact form submission from ${email}`,
|
|
165
|
+
text: message,
|
|
166
|
+
from: 'noreply@example.com',
|
|
167
|
+
replyTo: email
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return { success: true };
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error('Email error:', error);
|
|
173
|
+
return { success: false, error: 'Failed to send message' };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Best Practices
|
|
180
|
+
|
|
181
|
+
1. **Never expose AWS credentials in client-side code**
|
|
182
|
+
2. **Use environment variables for configuration**
|
|
183
|
+
3. **Verify sender addresses in SES before deploying**
|
|
184
|
+
4. **Always provide both `text` and `html` versions when sending HTML emails**
|
|
185
|
+
5. **Handle errors gracefully and log failures for monitoring**
|
|
186
|
+
6. **Consider rate limits** - SES has sending limits based on your account status
|
|
187
|
+
7. **Use BCC for bulk emails** to avoid exposing all recipients
|
|
188
|
+
8. **Set appropriate Reply-To addresses** for better user experience
|
|
189
|
+
|
|
190
|
+
## Troubleshooting
|
|
191
|
+
|
|
192
|
+
### "Sender not verified" error
|
|
193
|
+
|
|
194
|
+
Verify your sender email address in the AWS SES console for your region.
|
|
195
|
+
|
|
196
|
+
### "Access Denied" error
|
|
197
|
+
|
|
198
|
+
Ensure your AWS credentials have the `ses:SendEmail` permission.
|
|
199
|
+
|
|
200
|
+
### Emails not arriving
|
|
201
|
+
|
|
202
|
+
- Check SES sending statistics in AWS console
|
|
203
|
+
- Verify you're not in sandbox mode (or that recipients are verified)
|
|
204
|
+
- Check spam folders
|
|
205
|
+
- Review bounce and complaint notifications in SES
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { sendEmail, type SendEmailOptions } from './ses.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { sendEmail } from './ses.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface SendEmailOptions {
|
|
2
|
+
to: string | string[];
|
|
3
|
+
cc?: string | string[];
|
|
4
|
+
bcc?: string | string[];
|
|
5
|
+
subject: string;
|
|
6
|
+
text?: string;
|
|
7
|
+
html?: string;
|
|
8
|
+
from: string;
|
|
9
|
+
fromName?: string;
|
|
10
|
+
replyTo?: string | string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Send an email using AWS SES.
|
|
14
|
+
*
|
|
15
|
+
* Environment variables required:
|
|
16
|
+
* - AWS_REGION
|
|
17
|
+
* - AWS_ACCESS_KEY_ID
|
|
18
|
+
* - AWS_SECRET_ACCESS_KEY
|
|
19
|
+
*
|
|
20
|
+
* @returns messageId returned by SES
|
|
21
|
+
*/
|
|
22
|
+
export declare function sendEmail(options: SendEmailOptions): Promise<string>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';
|
|
2
|
+
// Create SES client once at module level for reuse across function calls
|
|
3
|
+
const sesClient = new SESClient({ region: process.env.AWS_REGION });
|
|
4
|
+
/**
|
|
5
|
+
* Send an email using AWS SES.
|
|
6
|
+
*
|
|
7
|
+
* Environment variables required:
|
|
8
|
+
* - AWS_REGION
|
|
9
|
+
* - AWS_ACCESS_KEY_ID
|
|
10
|
+
* - AWS_SECRET_ACCESS_KEY
|
|
11
|
+
*
|
|
12
|
+
* @returns messageId returned by SES
|
|
13
|
+
*/
|
|
14
|
+
export async function sendEmail(options) {
|
|
15
|
+
if (!options)
|
|
16
|
+
throw new Error('sendEmail: options is required');
|
|
17
|
+
const { to, cc, bcc, subject, text, html, from, fromName, replyTo } = options;
|
|
18
|
+
if (!subject) {
|
|
19
|
+
throw new Error('sendEmail: subject is required');
|
|
20
|
+
}
|
|
21
|
+
if (!text && !html) {
|
|
22
|
+
throw new Error('sendEmail: at least one of text or html body must be provided');
|
|
23
|
+
}
|
|
24
|
+
if (!from) {
|
|
25
|
+
throw new Error('sendEmail: sender `from` is required');
|
|
26
|
+
}
|
|
27
|
+
// Normalize and validate addresses: convert to array and filter out empty/whitespace strings
|
|
28
|
+
const normalizeAddresses = (addr) => {
|
|
29
|
+
if (addr === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
const addresses = Array.isArray(addr) ? addr : [addr];
|
|
32
|
+
const filtered = addresses.filter((a) => a && a.trim() !== '');
|
|
33
|
+
return filtered.length > 0 ? filtered : undefined;
|
|
34
|
+
};
|
|
35
|
+
const toAddresses = normalizeAddresses(to);
|
|
36
|
+
const ccAddresses = normalizeAddresses(cc);
|
|
37
|
+
const bccAddresses = normalizeAddresses(bcc);
|
|
38
|
+
const replyToAddresses = normalizeAddresses(replyTo);
|
|
39
|
+
if (!toAddresses || toAddresses.length === 0) {
|
|
40
|
+
throw new Error('sendEmail: at least one valid recipient is required (to)');
|
|
41
|
+
}
|
|
42
|
+
// Format source with optional fromName
|
|
43
|
+
const source = fromName ? `${fromName} <${from}>` : from;
|
|
44
|
+
// Build message body
|
|
45
|
+
const Message = {
|
|
46
|
+
Subject: {
|
|
47
|
+
Charset: 'UTF-8',
|
|
48
|
+
Data: subject
|
|
49
|
+
},
|
|
50
|
+
Body: {}
|
|
51
|
+
};
|
|
52
|
+
if (html) {
|
|
53
|
+
Message.Body.Html = {
|
|
54
|
+
Charset: 'UTF-8',
|
|
55
|
+
Data: html
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (text) {
|
|
59
|
+
Message.Body.Text = {
|
|
60
|
+
Charset: 'UTF-8',
|
|
61
|
+
Data: text
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const params = {
|
|
65
|
+
Source: source,
|
|
66
|
+
Destination: {
|
|
67
|
+
ToAddresses: toAddresses,
|
|
68
|
+
CcAddresses: ccAddresses,
|
|
69
|
+
BccAddresses: bccAddresses
|
|
70
|
+
},
|
|
71
|
+
Message,
|
|
72
|
+
ReplyToAddresses: replyToAddresses
|
|
73
|
+
};
|
|
74
|
+
try {
|
|
75
|
+
const command = new SendEmailCommand(params);
|
|
76
|
+
const res = await sesClient.send(command);
|
|
77
|
+
if (!res.MessageId) {
|
|
78
|
+
throw new Error('sendEmail: SES response did not contain a MessageId');
|
|
79
|
+
}
|
|
80
|
+
return res.MessageId;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
// Normalize error for callers
|
|
84
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
85
|
+
let code;
|
|
86
|
+
if (err && typeof err === 'object') {
|
|
87
|
+
const e = err;
|
|
88
|
+
if (typeof e['name'] === 'string')
|
|
89
|
+
code = e['name'];
|
|
90
|
+
}
|
|
91
|
+
const details = { message, code };
|
|
92
|
+
// Re-throw a clear error for the caller to handle
|
|
93
|
+
throw new Error(`sendEmail: failed to send email: ${JSON.stringify(details)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.7.
|
|
6
|
+
"version": "0.7.1",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
@@ -43,6 +43,10 @@
|
|
|
43
43
|
"types": "./dist/utils/form/index.d.ts",
|
|
44
44
|
"import": "./dist/utils/form/index.js"
|
|
45
45
|
},
|
|
46
|
+
"./utils/email": {
|
|
47
|
+
"types": "./dist/utils/email/index.d.ts",
|
|
48
|
+
"import": "./dist/utils/email/index.js"
|
|
49
|
+
},
|
|
46
50
|
"./server/form-handler": {
|
|
47
51
|
"types": "./dist/server/form-handler.d.ts",
|
|
48
52
|
"import": "./dist/server/form-handler.js"
|
|
@@ -102,6 +106,7 @@
|
|
|
102
106
|
"svelte"
|
|
103
107
|
],
|
|
104
108
|
"dependencies": {
|
|
109
|
+
"@aws-sdk/client-ses": "^3.948.0",
|
|
105
110
|
"@internationalized/date": "^3.10.0",
|
|
106
111
|
"@lucide/svelte": "^0.553.0",
|
|
107
112
|
"@sveltejs/adapter-auto": "^7.0.0",
|