vybekiit 0.7.0 → 0.7.2
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.
- package/dist/bin.js +1697 -562
- package/dist/global-skills/add-ai/SKILL.md +1 -1
- package/dist/global-skills/add-analytics/SKILL.md +1 -1
- package/dist/global-skills/add-blog/SKILL.md +1 -1
- package/dist/global-skills/add-crud/SKILL.md +1 -1
- package/dist/global-skills/add-files/SKILL.md +1 -1
- package/dist/global-skills/add-images/SKILL.md +1 -1
- package/dist/global-skills/add-language/SKILL.md +1 -1
- package/dist/global-skills/add-notifications/SKILL.md +1 -1
- package/dist/global-skills/add-realtime/SKILL.md +1 -1
- package/dist/global-skills/add-route/SKILL.md +1 -1
- package/dist/global-skills/add-search/SKILL.md +1 -1
- package/dist/global-skills/add-signin/SKILL.md +1 -1
- package/dist/global-skills/add-teams/SKILL.md +1 -1
- package/dist/global-skills/add-upload/SKILL.md +1 -1
- package/dist/global-skills/aws-serverless/SKILL.md +44 -44
- package/dist/global-skills/aws-serverless/assets/powertools-handler.py +1 -2
- package/dist/global-skills/aws-serverless/references/api-gateway.md +470 -50
- package/dist/global-skills/aws-serverless/references/architecture.md +186 -47
- package/dist/global-skills/aws-serverless/references/concurrency.md +158 -44
- package/dist/global-skills/aws-serverless/references/deployment.md +1 -1
- package/dist/global-skills/aws-serverless/references/event-sources.md +391 -72
- package/dist/global-skills/aws-serverless/references/lambda.md +428 -69
- package/dist/global-skills/aws-serverless/references/orchestration.md +384 -65
- package/dist/global-skills/aws-serverless/references/production.md +415 -78
- package/dist/global-skills/aws-serverless/references/troubleshooting.md +626 -79
- package/dist/global-skills/back-up-my-code/SKILL.md +1 -1
- package/dist/global-skills/buy-domain/SKILL.md +1 -1
- package/dist/global-skills/check-safety/SKILL.md +1 -1
- package/dist/global-skills/configure-capabilities/SKILL.md +1 -1
- package/dist/global-skills/connect-account/SKILL.md +1 -1
- package/dist/global-skills/connect-account-backend/SKILL.md +1 -1
- package/dist/global-skills/design-my-data/SKILL.md +1 -1
- package/dist/global-skills/doctor/SKILL.md +1 -1
- package/dist/global-skills/feedback/SKILL.md +63 -0
- package/dist/global-skills/go-live/SKILL.md +1 -1
- package/dist/global-skills/harden/SKILL.md +1 -1
- package/dist/global-skills/instrument-feature-flags/references/usage.md +0 -35
- package/dist/global-skills/instrument-product-analytics/SKILL.md +1 -1
- package/dist/global-skills/instrument-product-analytics/references/android.md +0 -36
- package/dist/global-skills/instrument-product-analytics/references/configuration.md +0 -1
- package/dist/global-skills/instrument-product-analytics/references/flutter.md +0 -37
- package/dist/global-skills/instrument-product-analytics/references/posthog-python.md +2 -3
- package/dist/global-skills/instrument-product-analytics/references/usage.md +0 -35
- package/dist/global-skills/manifest.json +2 -1
- package/dist/global-skills/neon/SKILL.md +20 -27
- package/dist/global-skills/neon-ai-gateway/SKILL.md +2 -68
- package/dist/global-skills/neon-functions/SKILL.md +7 -7
- package/dist/global-skills/neon-object-storage/SKILL.md +2 -2
- package/dist/global-skills/neon-postgres/SKILL.md +5 -5
- package/dist/global-skills/neon-postgres-branches/SKILL.md +1 -1
- package/dist/global-skills/onboarding/SKILL.md +1 -1
- package/dist/global-skills/plan-my-idea/SKILL.md +1 -1
- package/dist/global-skills/publish-app/SKILL.md +1 -1
- package/dist/global-skills/publish-extension/SKILL.md +1 -1
- package/dist/global-skills/reset-password/SKILL.md +1 -1
- package/dist/global-skills/save-data/SKILL.md +1 -1
- package/dist/global-skills/setup-email/SKILL.md +1 -1
- package/dist/global-skills/setup-payments/SKILL.md +1 -1
- package/dist/global-skills/setup-sms/SKILL.md +1 -1
- package/dist/global-skills/sign-in-with-email-link/SKILL.md +1 -1
- package/dist/global-skills/sign-in-with-google/SKILL.md +1 -1
- package/dist/global-skills/sign-in-with-phone/SKILL.md +1 -1
- package/dist/global-skills/stripe-best-practices/SKILL.md +6 -11
- package/dist/global-skills/stripe-best-practices/references/billing.md +0 -5
- package/dist/global-skills/stripe-best-practices/references/payments.md +2 -4
- package/dist/global-skills/stripe-best-practices/references/tax.md +8 -78
- package/dist/global-skills/track-errors/SKILL.md +1 -1
- package/dist/global-skills/update-kit/SKILL.md +1 -1
- package/dist/global-skills/wire-auth/SKILL.md +1 -1
- package/dist/global-skills/wire-database/SKILL.md +1 -1
- package/dist/global-skills/wire-email/SKILL.md +1 -1
- package/dist/global-skills/wire-payments/SKILL.md +1 -1
- package/package.json +8 -8
- package/dist/global-skills/email-best-practices/.github/workflows/sync-skills.yml +0 -30
- package/dist/global-skills/email-best-practices/README.md +0 -63
- package/dist/global-skills/email-best-practices/references/accessibility.md +0 -189
- package/dist/global-skills/email-best-practices/references/compliance.md +0 -125
- package/dist/global-skills/email-best-practices/references/deliverability.md +0 -121
- package/dist/global-skills/email-best-practices/references/email-capture.md +0 -129
- package/dist/global-skills/email-best-practices/references/email-types.md +0 -173
- package/dist/global-skills/email-best-practices/references/list-management.md +0 -157
- package/dist/global-skills/email-best-practices/references/marketing-emails.md +0 -115
- package/dist/global-skills/email-best-practices/references/sending-reliability.md +0 -155
- package/dist/global-skills/email-best-practices/references/transactional-email-catalog.md +0 -418
- package/dist/global-skills/email-best-practices/references/transactional-emails.md +0 -92
- package/dist/global-skills/email-best-practices/references/webhooks-events.md +0 -167
- package/dist/global-skills/email-best-practices/tests/README.md +0 -35
- package/dist/global-skills/email-best-practices/tests/scenarios/01-spam-deliverability.md +0 -46
- package/dist/global-skills/email-best-practices/tests/scenarios/02-multi-region-compliance.md +0 -48
- package/dist/global-skills/email-best-practices/tests/scenarios/03-retry-idempotency.md +0 -36
- package/dist/global-skills/email-best-practices/tests/scenarios/04-webhook-bounce-handling.md +0 -52
- package/dist/global-skills/email-best-practices/tests/scenarios/05-new-saas-email-plan.md +0 -51
- package/dist/global-skills/neon-postgres/references/neon-sdk.md +0 -262
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
# Email Types: Transactional vs Marketing
|
|
2
|
-
|
|
3
|
-
Understanding the difference between transactional and marketing emails is crucial for compliance, deliverability, and user experience. This guide explains the distinctions and provides a catalog of transactional emails your app should include.
|
|
4
|
-
|
|
5
|
-
## When to Use This
|
|
6
|
-
|
|
7
|
-
- Deciding whether an email should be transactional or marketing
|
|
8
|
-
- Understanding legal distinctions between email types
|
|
9
|
-
- Planning what transactional emails your app needs
|
|
10
|
-
- Ensuring compliance with email regulations
|
|
11
|
-
- Setting up separate sending infrastructure
|
|
12
|
-
|
|
13
|
-
## Transactional vs Marketing: Key Differences
|
|
14
|
-
|
|
15
|
-
### Transactional Emails
|
|
16
|
-
|
|
17
|
-
**Definition:** Emails that facilitate or confirm a transaction the user initiated or expects. They're directly related to an action the user took or are legal notices you're required to serve.
|
|
18
|
-
|
|
19
|
-
**Characteristics:**
|
|
20
|
-
- User-initiated or expected
|
|
21
|
-
- Time-sensitive and actionable
|
|
22
|
-
- Required for the user to complete an action
|
|
23
|
-
- Does not include promotional material or offers
|
|
24
|
-
- Can be sent without explicit opt-in (with limitations)
|
|
25
|
-
|
|
26
|
-
**Examples:**
|
|
27
|
-
- Password reset links
|
|
28
|
-
- Order confirmations
|
|
29
|
-
- Account verification
|
|
30
|
-
- OTP/2FA codes
|
|
31
|
-
- Shipping notifications
|
|
32
|
-
|
|
33
|
-
**Analogy:**
|
|
34
|
-
Think of transactional emails for everything that would leave you with a paper receipt in the real world: invoices, parking ticket, booking confirmation, etc.
|
|
35
|
-
|
|
36
|
-
### Marketing Emails
|
|
37
|
-
|
|
38
|
-
**Definition:** Emails sent for promotional, advertising, or informational purposes that are not directly related to a specific transaction or legal requirement.
|
|
39
|
-
|
|
40
|
-
**Characteristics:**
|
|
41
|
-
- Promotional or informational content
|
|
42
|
-
- Not time-sensitive to complete a transaction
|
|
43
|
-
- Require explicit opt-in (consent)
|
|
44
|
-
- Must include unsubscribe options
|
|
45
|
-
- Subject to stricter compliance requirements
|
|
46
|
-
|
|
47
|
-
**Examples:**
|
|
48
|
-
- Newsletters
|
|
49
|
-
- Abandoned cart
|
|
50
|
-
- Product announcements
|
|
51
|
-
- Promotional offers
|
|
52
|
-
- Company updates
|
|
53
|
-
- Educational content
|
|
54
|
-
|
|
55
|
-
## Legal Distinctions
|
|
56
|
-
|
|
57
|
-
### CAN-SPAM Act (US)
|
|
58
|
-
|
|
59
|
-
**Transactional emails:**
|
|
60
|
-
- Can be sent without opt-in
|
|
61
|
-
- Must be related to a transaction
|
|
62
|
-
- Cannot contain promotional content (with exceptions)
|
|
63
|
-
- Must identify sender and provide contact information
|
|
64
|
-
|
|
65
|
-
**Marketing emails:**
|
|
66
|
-
- Require opt-out mechanism (not opt-in in US)
|
|
67
|
-
- Must include clear sender identification
|
|
68
|
-
- Must include physical mailing address
|
|
69
|
-
- Must honor opt-out requests within 10 business days
|
|
70
|
-
|
|
71
|
-
### GDPR (EU)
|
|
72
|
-
|
|
73
|
-
**Transactional emails:**
|
|
74
|
-
- Can be sent based on legitimate interest or contract fulfillment
|
|
75
|
-
- Must be necessary for service delivery
|
|
76
|
-
- Cannot contain marketing content without consent
|
|
77
|
-
|
|
78
|
-
**Marketing emails:**
|
|
79
|
-
- Require explicit opt-in consent
|
|
80
|
-
- Must clearly state purpose of data collection
|
|
81
|
-
- Must provide easy unsubscribe
|
|
82
|
-
- Subject to data protection requirements
|
|
83
|
-
|
|
84
|
-
### CASL (Canada)
|
|
85
|
-
|
|
86
|
-
**Transactional emails:**
|
|
87
|
-
- Can be sent without consent if related to ongoing business relationship
|
|
88
|
-
- Must be factual and not promotional
|
|
89
|
-
|
|
90
|
-
**Marketing emails:**
|
|
91
|
-
- Require express or implied consent
|
|
92
|
-
- Must include unsubscribe mechanism
|
|
93
|
-
- Must identify sender clearly
|
|
94
|
-
|
|
95
|
-
## When to Use Each Type
|
|
96
|
-
|
|
97
|
-
### Use Transactional When:
|
|
98
|
-
|
|
99
|
-
- User needs the email to complete an action
|
|
100
|
-
- Email confirms a transaction or account change
|
|
101
|
-
- Email provides security-related information
|
|
102
|
-
- Email is expected based on user action
|
|
103
|
-
- Content is time-sensitive and actionable
|
|
104
|
-
- You're required to serve a notification for compliance
|
|
105
|
-
|
|
106
|
-
### Use Marketing When:
|
|
107
|
-
|
|
108
|
-
- Promoting products or services
|
|
109
|
-
- Sending newsletters or updates
|
|
110
|
-
- Sharing educational content
|
|
111
|
-
- Announcing features or company news
|
|
112
|
-
- Content is not required for a transaction
|
|
113
|
-
|
|
114
|
-
## Hybrid Emails: The Gray Area
|
|
115
|
-
|
|
116
|
-
Some emails mix transactional and marketing content. This isn't best practice and should be avoided.
|
|
117
|
-
|
|
118
|
-
**Best practice:** Keep transactional and marketing separate.
|
|
119
|
-
|
|
120
|
-
**Example of problematic hybrid:**
|
|
121
|
-
- Newsletter (marketing) with a small order status update (transactional)
|
|
122
|
-
|
|
123
|
-
## Transactional Email Catalog
|
|
124
|
-
|
|
125
|
-
For a complete catalog of transactional emails and recommended combinations by app type, see [Transactional Email Catalog](./transactional-email-catalog.md).
|
|
126
|
-
|
|
127
|
-
**Quick reference - Essential emails for most apps:**
|
|
128
|
-
1. **Email verification** - Required for account creation
|
|
129
|
-
2. **Password reset** - Required for account recovery
|
|
130
|
-
3. **Welcome email** - Good user experience
|
|
131
|
-
|
|
132
|
-
The catalog includes detailed guidance for:
|
|
133
|
-
- Authentication-focused apps
|
|
134
|
-
- Newsletter / content platforms
|
|
135
|
-
- E-commerce / marketplaces
|
|
136
|
-
- SaaS / subscription services
|
|
137
|
-
- Financial / fintech apps
|
|
138
|
-
- Social / community platforms
|
|
139
|
-
- Developer tools / API platforms
|
|
140
|
-
- Healthcare / HIPAA-compliant apps
|
|
141
|
-
|
|
142
|
-
## Sending Infrastructure
|
|
143
|
-
|
|
144
|
-
### Separate subdomains
|
|
145
|
-
|
|
146
|
-
**Best practice:** Use separate sending subdomains for transactional and marketing emails.
|
|
147
|
-
|
|
148
|
-
**Benefits:**
|
|
149
|
-
- Protect transactional deliverability
|
|
150
|
-
- Different authentication domains
|
|
151
|
-
- Independent reputation
|
|
152
|
-
- Easier compliance management
|
|
153
|
-
|
|
154
|
-
**Implementation:**
|
|
155
|
-
- Use different subdomains (e.g., `t.example.com` for transactional, `m.example.com` for marketing)
|
|
156
|
-
|
|
157
|
-
### Email Service Considerations
|
|
158
|
-
|
|
159
|
-
Choose an email service that:
|
|
160
|
-
- Provides reliable delivery for transactional emails
|
|
161
|
-
- Offers separate sending domains
|
|
162
|
-
- Has good API for programmatic sending
|
|
163
|
-
- Provides webhooks for delivery events
|
|
164
|
-
- Supports authentication setup (SPF, DKIM, DMARC)
|
|
165
|
-
|
|
166
|
-
Services like Resend are designed for transactional emails and provide the infrastructure and tools needed for reliable delivery. They also offer powerful marketing features.
|
|
167
|
-
|
|
168
|
-
## Related Topics
|
|
169
|
-
|
|
170
|
-
- [Transactional Emails](./transactional-emails.md) - Best practices for sending transactional emails
|
|
171
|
-
- [Marketing Emails](./marketing-emails.md) - Best practices for marketing emails
|
|
172
|
-
- [Compliance](./compliance.md) - Legal requirements for each email type
|
|
173
|
-
- [Deliverability](./deliverability.md) - Ensuring transactional emails are delivered
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
# List Management
|
|
2
|
-
|
|
3
|
-
Maintaining clean email lists through suppression, hygiene, and data retention.
|
|
4
|
-
|
|
5
|
-
## Suppression Lists
|
|
6
|
-
|
|
7
|
-
A suppression list prevents sending to addresses that should never receive email.
|
|
8
|
-
|
|
9
|
-
### What to Suppress
|
|
10
|
-
|
|
11
|
-
| Reason | Action | Can Unsuppress? |
|
|
12
|
-
|--------|--------|-----------------|
|
|
13
|
-
| Hard bounce | Add immediately | No (address invalid) |
|
|
14
|
-
| Complaint (spam) | Add immediately | No (legal requirement) |
|
|
15
|
-
| Soft bounce (3x) | Add after threshold | Yes, after 30-90 days |
|
|
16
|
-
| Manual removal | Add on request | Only if user requests |
|
|
17
|
-
|
|
18
|
-
### Implementation
|
|
19
|
-
|
|
20
|
-
```typescript
|
|
21
|
-
// Suppression list schema
|
|
22
|
-
interface SuppressionEntry {
|
|
23
|
-
email: string;
|
|
24
|
-
reason: 'hard_bounce' | 'complaint' | 'unsubscribe' | 'soft_bounce' | 'manual';
|
|
25
|
-
created_at: Date;
|
|
26
|
-
source_email_id?: string; // Which email triggered this
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Check before every send
|
|
30
|
-
async function canSendTo(email: string): Promise<boolean> {
|
|
31
|
-
const suppressed = await db.suppressions.findOne({ email });
|
|
32
|
-
return !suppressed;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Add to suppression list
|
|
36
|
-
async function suppressEmail(email: string, reason: string, sourceId?: string) {
|
|
37
|
-
await db.suppressions.upsert({
|
|
38
|
-
email: email.toLowerCase(),
|
|
39
|
-
reason,
|
|
40
|
-
created_at: new Date(),
|
|
41
|
-
source_email_id: sourceId,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Pre-Send Check
|
|
47
|
-
|
|
48
|
-
**Always check suppression before sending:**
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
async function sendEmail(to: string, emailData: EmailData) {
|
|
52
|
-
if (!await canSendTo(to)) {
|
|
53
|
-
console.log(`Skipping suppressed email: ${to}`);
|
|
54
|
-
return { skipped: true, reason: 'suppressed' };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return await resend.emails.send({ to, ...emailData });
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## List Hygiene
|
|
62
|
-
|
|
63
|
-
Regular maintenance to keep lists healthy.
|
|
64
|
-
|
|
65
|
-
### Automated Cleanup
|
|
66
|
-
|
|
67
|
-
| Task | Frequency | Action |
|
|
68
|
-
|------|-----------|--------|
|
|
69
|
-
| Remove hard bounces | Real-time (via webhook) | Immediate suppression |
|
|
70
|
-
| Remove complaints | Real-time (via webhook) | Immediate suppression |
|
|
71
|
-
| Process unsubscribes | Real-time | Remove from marketing lists |
|
|
72
|
-
| Review soft bounces | Daily | Suppress after 3 failures |
|
|
73
|
-
| Remove inactive | Monthly | Re-engagement → remove |
|
|
74
|
-
|
|
75
|
-
Learn more: https://resend.com/docs/knowledge-base/audience-hygiene
|
|
76
|
-
|
|
77
|
-
### Re-engagement Campaigns
|
|
78
|
-
|
|
79
|
-
Before removing inactive subscribers:
|
|
80
|
-
|
|
81
|
-
1. **Identify inactive:** No opens/clicks in 45-90 days
|
|
82
|
-
2. **Send re-engagement:** "We miss you" or "Still interested?"
|
|
83
|
-
3. **Wait 14-30 days** for response
|
|
84
|
-
4. **Remove non-responders** from active lists
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
async function runReengagement() {
|
|
88
|
-
const inactive = await getInactiveSubscribers(90); // 90 days
|
|
89
|
-
|
|
90
|
-
for (const subscriber of inactive) {
|
|
91
|
-
if (!subscriber.reengagement_sent) {
|
|
92
|
-
await sendReengagementEmail(subscriber);
|
|
93
|
-
await markReengagementSent(subscriber.email);
|
|
94
|
-
} else if (daysSince(subscriber.reengagement_sent) > 30) {
|
|
95
|
-
await removeFromMarketingLists(subscriber.email);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Data Retention
|
|
102
|
-
|
|
103
|
-
### Email Logs
|
|
104
|
-
|
|
105
|
-
| Data Type | Recommended Retention | Notes |
|
|
106
|
-
|-----------|----------------------|-------|
|
|
107
|
-
| Send attempts | 90 days | Debugging, analytics |
|
|
108
|
-
| Delivery status | 90 days | Compliance, reporting |
|
|
109
|
-
| Bounce/complaint events | 3 years | Required for CASL |
|
|
110
|
-
| Suppression list | Indefinite | Never delete |
|
|
111
|
-
| Email content | 30 days | Storage costs |
|
|
112
|
-
| Consent records | 3 years after expiry | Legal requirement |
|
|
113
|
-
|
|
114
|
-
### Retention Policy Implementation
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
// Daily cleanup job
|
|
118
|
-
async function cleanupOldData() {
|
|
119
|
-
const now = new Date();
|
|
120
|
-
|
|
121
|
-
// Delete old email logs (keep 90 days)
|
|
122
|
-
await db.emailLogs.deleteMany({
|
|
123
|
-
created_at: { $lt: subDays(now, 90) }
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// Delete old email content (keep 30 days)
|
|
127
|
-
await db.emailContent.deleteMany({
|
|
128
|
-
created_at: { $lt: subDays(now, 30) }
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Never delete: suppressions, consent records
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Metrics to Monitor
|
|
136
|
-
|
|
137
|
-
| Metric | Target | Alert Threshold |
|
|
138
|
-
|--------|--------|-----------------|
|
|
139
|
-
| Bounce rate | <2% | >2% |
|
|
140
|
-
| Complaint rate | <0.05% | >0.05% |
|
|
141
|
-
| Suppression list growth | Stable | Sudden spike |
|
|
142
|
-
|
|
143
|
-
## Transactional vs Marketing Lists
|
|
144
|
-
|
|
145
|
-
**Keep separate:**
|
|
146
|
-
- Transactional: Can send to anyone with account relationship
|
|
147
|
-
- Marketing: Only opted-in subscribers
|
|
148
|
-
|
|
149
|
-
**Suppression applies to both:** Hard bounces and complaints suppress across all email types.
|
|
150
|
-
|
|
151
|
-
**Unsubscribe is marketing-only:** User unsubscribing from marketing can still receive transactional emails (password resets, order confirmations).
|
|
152
|
-
|
|
153
|
-
## Related
|
|
154
|
-
|
|
155
|
-
- [Webhooks & Events](./webhooks-events.md) - Receive bounce/complaint notifications
|
|
156
|
-
- [Deliverability](./deliverability.md) - How list hygiene affects sender reputation
|
|
157
|
-
- [Compliance](./compliance.md) - Legal requirements for data retention
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
# Marketing Email Best Practices
|
|
2
|
-
|
|
3
|
-
Promotional emails that require explicit consent and provide value to recipients.
|
|
4
|
-
|
|
5
|
-
## Core Principles
|
|
6
|
-
|
|
7
|
-
1. **Consent first** - Explicit opt-in required (especially GDPR/CASL)
|
|
8
|
-
2. **Value-driven** - Provide useful content, not just promotions
|
|
9
|
-
3. **Respect preferences** - Let users control frequency and content types
|
|
10
|
-
|
|
11
|
-
## Opt-In Requirements
|
|
12
|
-
|
|
13
|
-
### Explicit Opt-In
|
|
14
|
-
|
|
15
|
-
**What counts:**
|
|
16
|
-
- User checks unchecked box
|
|
17
|
-
- User clicks "Subscribe" button
|
|
18
|
-
- User completes form with clear subscription intent
|
|
19
|
-
|
|
20
|
-
**What doesn't count:**
|
|
21
|
-
- Pre-checked boxes
|
|
22
|
-
- Opt-out model
|
|
23
|
-
- Assumed consent from purchase
|
|
24
|
-
- Purchased/rented lists
|
|
25
|
-
|
|
26
|
-
### Informed Consent
|
|
27
|
-
|
|
28
|
-
Disclose: email types, frequency, sender identity, how to unsubscribe.
|
|
29
|
-
|
|
30
|
-
✅ "Subscribe to our weekly newsletter with product updates and tips"
|
|
31
|
-
❌ "Sign up for emails"
|
|
32
|
-
|
|
33
|
-
### Double Opt-In (Recommended)
|
|
34
|
-
|
|
35
|
-
1. User submits email
|
|
36
|
-
2. Send confirmation email with verification link
|
|
37
|
-
3. User clicks to confirm
|
|
38
|
-
4. Add to list only after confirmation
|
|
39
|
-
|
|
40
|
-
Benefits: Verifies deliverability, confirms intent, reduces complaints, required in some regions (Germany).
|
|
41
|
-
|
|
42
|
-
## Unsubscribe Requirements
|
|
43
|
-
|
|
44
|
-
**Must be:**
|
|
45
|
-
- Prominent in every email
|
|
46
|
-
- One-click (preferred)
|
|
47
|
-
- Immediate (GDPR) or within 10 days (CAN-SPAM) (immediate preferred)
|
|
48
|
-
- Free, no login required
|
|
49
|
-
|
|
50
|
-
**Preference center options:** Frequency (daily/weekly/monthly), content types, complete unsubscribe.
|
|
51
|
-
|
|
52
|
-
## Content and Design
|
|
53
|
-
|
|
54
|
-
### Subject Lines
|
|
55
|
-
|
|
56
|
-
- Clear and specific (50 chars or less for mobile)
|
|
57
|
-
- Create curiosity without misleading
|
|
58
|
-
- A/B test regularly
|
|
59
|
-
|
|
60
|
-
✅ "Your weekly digest: 5 productivity tips"
|
|
61
|
-
❌ "You won't believe what happened!"
|
|
62
|
-
|
|
63
|
-
### Structure
|
|
64
|
-
|
|
65
|
-
**Above fold:** Value proposition, primary CTA, engaging visual
|
|
66
|
-
|
|
67
|
-
**Body:** Scannable (short paragraphs, bullets), clear hierarchy, multiple CTAs
|
|
68
|
-
|
|
69
|
-
**Footer:** Unsubscribe link, company info, physical address (CAN-SPAM), social links
|
|
70
|
-
|
|
71
|
-
### Mobile-First
|
|
72
|
-
|
|
73
|
-
- Single column layout
|
|
74
|
-
- 44x44px minimum buttons
|
|
75
|
-
- 16px minimum text
|
|
76
|
-
- Test on iOS, Android, dark mode
|
|
77
|
-
|
|
78
|
-
## Segmentation
|
|
79
|
-
|
|
80
|
-
**Segment by:** Behavior (purchases, activity), demographics, preferences, engagement level, signup source.
|
|
81
|
-
|
|
82
|
-
Benefits: Higher open/click rates, lower unsubscribes, better experience.
|
|
83
|
-
|
|
84
|
-
## Personalization
|
|
85
|
-
|
|
86
|
-
**Options:** Name in subject/greeting, location-specific content, behavior-based recommendations, purchase history.
|
|
87
|
-
|
|
88
|
-
**Don't over-personalize** - can feel intrusive. Use data you have permission to use.
|
|
89
|
-
|
|
90
|
-
## Frequency and Timing
|
|
91
|
-
|
|
92
|
-
**Frequency:** Start conservative, increase based on engagement, let users set preferences, monitor unsubscribe rates.
|
|
93
|
-
|
|
94
|
-
**Timing:** Weekday mornings (9-11 AM local), Tuesday-Thursday often best. Test your specific audience.
|
|
95
|
-
|
|
96
|
-
## List Hygiene
|
|
97
|
-
|
|
98
|
-
**Remove immediately:** Hard bounces, unsubscribes, complaints
|
|
99
|
-
|
|
100
|
-
**Remove after inactivity:** Send re-engagement campaign first, then remove non-responders
|
|
101
|
-
|
|
102
|
-
**Monitor:** Bounce rate <2%, complaint rate <0.05%
|
|
103
|
-
|
|
104
|
-
## Required Elements (All Marketing Emails)
|
|
105
|
-
|
|
106
|
-
- Clear sender identification
|
|
107
|
-
- Physical mailing address (CAN-SPAM)
|
|
108
|
-
- Unsubscribe mechanism
|
|
109
|
-
- Indication it's marketing (GDPR)
|
|
110
|
-
|
|
111
|
-
## Related
|
|
112
|
-
|
|
113
|
-
- [Compliance](./compliance.md) - Detailed legal requirements by region
|
|
114
|
-
- [Email Capture](./email-capture.md) - Collecting consent properly
|
|
115
|
-
- [List Management](./list-management.md) - Maintaining list hygiene
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
# Sending Reliability
|
|
2
|
-
|
|
3
|
-
Ensuring emails are sent exactly once and handling failures gracefully.
|
|
4
|
-
|
|
5
|
-
## Idempotency
|
|
6
|
-
|
|
7
|
-
Prevent duplicate emails when retrying failed requests.
|
|
8
|
-
|
|
9
|
-
### The Problem
|
|
10
|
-
|
|
11
|
-
Network issues, timeouts, or server errors can leave you uncertain if an email was sent. Retrying without idempotency risks sending duplicates.
|
|
12
|
-
|
|
13
|
-
### Solution: Idempotency Keys
|
|
14
|
-
|
|
15
|
-
Send a unique key with each request. If the same key is sent again, the server returns the original response instead of sending another email.
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
// Generate deterministic key based on the business event
|
|
19
|
-
const idempotencyKey = `password-reset-${userId}-${resetRequestId}`;
|
|
20
|
-
|
|
21
|
-
await resend.emails.send({
|
|
22
|
-
from: 'noreply@example.com',
|
|
23
|
-
to: user.email,
|
|
24
|
-
subject: 'Reset your password',
|
|
25
|
-
html: emailHtml,
|
|
26
|
-
}, {
|
|
27
|
-
headers: {
|
|
28
|
-
'Idempotency-Key': idempotencyKey
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### Key Generation Strategies
|
|
34
|
-
|
|
35
|
-
| Strategy | Example | Use When |
|
|
36
|
-
|----------|---------|----------|
|
|
37
|
-
| Event-based | `order-confirm-${orderId}` | One email per event (recommended) |
|
|
38
|
-
| Request-scoped | `reset-${userId}-${resetRequestId}` | Retries within same request |
|
|
39
|
-
| UUID | `crypto.randomUUID()` | No natural key (generate once, reuse on retry) |
|
|
40
|
-
|
|
41
|
-
**Best practice:** Use deterministic keys based on the business event. If you retry the same logical send, the same key must be generated. Avoid `Date.now()` or random values generated fresh on each attempt.
|
|
42
|
-
|
|
43
|
-
**Key expiration:** Idempotency keys are typically cached for 24 hours. Retries within this window return the original response. After expiration, the same key triggers a new send—so complete your retry logic well within 24 hours.
|
|
44
|
-
|
|
45
|
-
## Retry Logic
|
|
46
|
-
|
|
47
|
-
Handle transient failures with exponential backoff.
|
|
48
|
-
|
|
49
|
-
### When to Retry
|
|
50
|
-
|
|
51
|
-
| Error Type | Retry? | Notes |
|
|
52
|
-
|------------|--------|-------|
|
|
53
|
-
| 5xx (server error) | ✅ Yes | Transient, likely to resolve |
|
|
54
|
-
| 429 (rate limit) | ✅ Yes | Wait for rate limit window |
|
|
55
|
-
| 4xx (client error) | ❌ No | Fix the request first |
|
|
56
|
-
| Network timeout | ✅ Yes | Transient |
|
|
57
|
-
| DNS failure | ✅ Yes | May be transient |
|
|
58
|
-
|
|
59
|
-
### Exponential Backoff
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
async function sendWithRetry(emailData, maxRetries = 3) {
|
|
63
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
64
|
-
try {
|
|
65
|
-
return await resend.emails.send(emailData);
|
|
66
|
-
} catch (error) {
|
|
67
|
-
if (!isRetryable(error) || attempt === maxRetries - 1) {
|
|
68
|
-
throw error;
|
|
69
|
-
}
|
|
70
|
-
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
|
|
71
|
-
await sleep(delay + Math.random() * 1000); // Add jitter
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function isRetryable(error) {
|
|
77
|
-
return error.statusCode >= 500 ||
|
|
78
|
-
error.statusCode === 429 ||
|
|
79
|
-
error.code === 'ETIMEDOUT';
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**Backoff schedule:** 1s → 2s → 4s → 8s (with jitter to prevent thundering herd)
|
|
84
|
-
|
|
85
|
-
## Error Handling
|
|
86
|
-
|
|
87
|
-
### Common Error Codes
|
|
88
|
-
|
|
89
|
-
| Code | Meaning | Action |
|
|
90
|
-
|------|---------|--------|
|
|
91
|
-
| 400 | Bad request | Fix payload (invalid email, missing field) |
|
|
92
|
-
| 401 | Unauthorized | Check API key |
|
|
93
|
-
| 403 | Forbidden | Check permissions, domain verification |
|
|
94
|
-
| 404 | Not found | Check endpoint URL |
|
|
95
|
-
| 422 | Validation error | Fix request data |
|
|
96
|
-
| 429 | Rate limited | Back off, retry after delay |
|
|
97
|
-
| 500 | Server error | Retry with backoff |
|
|
98
|
-
| 503 | Service unavailable | Retry with backoff |
|
|
99
|
-
|
|
100
|
-
### Error Handling Pattern
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
try {
|
|
104
|
-
const result = await resend.emails.send(emailData);
|
|
105
|
-
await logSuccess(result.id, emailData);
|
|
106
|
-
} catch (error) {
|
|
107
|
-
if (error.statusCode === 429) {
|
|
108
|
-
await queueForRetry(emailData, error.retryAfter);
|
|
109
|
-
} else if (error.statusCode >= 500) {
|
|
110
|
-
await queueForRetry(emailData);
|
|
111
|
-
} else {
|
|
112
|
-
await logFailure(error, emailData);
|
|
113
|
-
await alertOnCriticalEmail(emailData); // For password resets, etc.
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Queuing for Reliability
|
|
119
|
-
|
|
120
|
-
For critical emails, use a queue to ensure delivery even if the initial send fails.
|
|
121
|
-
|
|
122
|
-
**Benefits:**
|
|
123
|
-
- Survives application restarts
|
|
124
|
-
- Automatic retry handling
|
|
125
|
-
- Rate limit management
|
|
126
|
-
- Audit trail
|
|
127
|
-
|
|
128
|
-
**Simple pattern:**
|
|
129
|
-
1. Write email to queue/database with "pending" status
|
|
130
|
-
2. Process queue, attempt send
|
|
131
|
-
3. On success: mark "sent", store message ID
|
|
132
|
-
4. On retryable failure: increment retry count, schedule retry
|
|
133
|
-
5. On permanent failure: mark "failed", alert
|
|
134
|
-
|
|
135
|
-
## Timeouts
|
|
136
|
-
|
|
137
|
-
Set appropriate timeouts to avoid hanging requests.
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
const controller = new AbortController();
|
|
141
|
-
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
await resend.emails.send(emailData, { signal: controller.signal });
|
|
145
|
-
} finally {
|
|
146
|
-
clearTimeout(timeout);
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
**Recommended:** 10-30 seconds for email API calls.
|
|
151
|
-
|
|
152
|
-
## Related
|
|
153
|
-
|
|
154
|
-
- [Webhooks & Events](./webhooks-events.md) - Process delivery confirmations and failures
|
|
155
|
-
- [List Management](./list-management.md) - Handle bounces and suppress invalid addresses
|