Skip to content

Commit 927eed4

Browse files
committed
Use http email service
1 parent a176952 commit 927eed4

7 files changed

Lines changed: 93 additions & 259 deletions

File tree

.env.example

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ SHARED_STORAGE_ACCESS_KEY_ID=your-shared-storage-access-key-id
2626
SHARED_STORAGE_SECRET_ACCESS_KEY=your-shared-storage-secret-access-key
2727
SHARED_STORAGE_PUBLIC_URL=https://your-shared-storage-public-url
2828

29-
# SMTP (optional - for password auth emails)
30-
SMTP_HOST=smtp.example.com
31-
SMTP_PORT=587
32-
SMTP_USER=your-smtp-user
33-
SMTP_PASS=your-smtp-password
34-
29+
# Email (optional - for password auth emails)
30+
EMAIL_PROVIDER=scaleway
31+
32+
SCW_SECRET_KEY=your-scaleway-secret-key
33+
SCW_PROJECT_ID=your-scaleway-project-id
34+
SCW_REGION=fr-par

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"cSpell.words": ["buildx", "croner", "healthcheck", "openworkers", "postgate", "timestamptz", "voxtral"]
2+
"cSpell.words": ["buildx", "croner", "healthcheck", "openworkers", "postgate", "scaleway", "timestamptz", "voxtral"]
33
}

bun.lock

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

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openworkers-api",
3-
"version": "1.2.2",
3+
"version": "1.2.3",
44
"license": "MIT",
55
"module": "src/index.ts",
66
"type": "module",
@@ -22,13 +22,11 @@
2222
"aws4fetch": "^1.0.20",
2323
"hono": "^4.10.6",
2424
"jszip": "^3.10.1",
25-
"nodemailer": "^7.0.12",
2625
"p-limit": "^7.2.0",
2726
"zod": "^4.1.12"
2827
},
2928
"devDependencies": {
3029
"@types/bun": "latest",
31-
"@types/nodemailer": "^7.0.4",
3230
"prettier": "^3.6.2",
3331
"zod-to-ts": "^2.0.0"
3432
},

src/config/index.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,14 @@ const ConfigSchema = z.object({
5959
apiKey: z.string().optional()
6060
}),
6161

62-
// SMTP for email (verification, password reset)
63-
smtp: z.object({
64-
host: z.string().optional(),
65-
port: z.coerce.number().int().positive().default(587),
66-
user: z.string().optional(),
67-
pass: z.string().optional(),
68-
from: z.string().default('[email protected]')
62+
// Email provider (verification, password reset)
63+
email: z.object({
64+
provider: z.enum(['scaleway']).optional(),
65+
from: z.string().default('[email protected]'),
66+
// Scaleway-specific
67+
secretKey: z.string().optional(),
68+
projectId: z.string().optional(),
69+
region: z.string().default('fr-par')
6970
}),
7071

7172
// App URLs for email links
@@ -112,12 +113,12 @@ function loadConfig(): Config {
112113
anthropic: {
113114
apiKey: process.env.ANTHROPIC_API_KEY
114115
},
115-
smtp: {
116-
host: process.env.SMTP_HOST,
117-
port: process.env.SMTP_PORT,
118-
user: process.env.SMTP_USER,
119-
pass: process.env.SMTP_PASS,
120-
from: process.env.SMTP_FROM
116+
email: {
117+
provider: process.env.EMAIL_PROVIDER,
118+
from: process.env.EMAIL_FROM,
119+
secretKey: process.env.SCW_SECRET_KEY,
120+
projectId: process.env.SCW_PROJECT_ID,
121+
region: process.env.SCW_REGION
121122
},
122123
appUrl: process.env.APP_URL
123124
};
@@ -137,9 +138,9 @@ function loadConfig(): Config {
137138
console.warn('GitHub OAuth not configured (GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET missing)');
138139
}
139140

140-
// Warn about missing SMTP
141-
if (!config.smtp.host) {
142-
console.warn('SMTP not configured (SMTP_HOST missing) - email features disabled');
141+
// Warn about missing email provider
142+
if (!config.email.provider) {
143+
console.warn('Email not configured (EMAIL_PROVIDER missing) - email features disabled');
143144
}
144145

145146
return config;
@@ -159,4 +160,4 @@ function loadConfig(): Config {
159160
export const config = loadConfig();
160161

161162
// Export individual sections for convenience
162-
export const { nodeEnv, port, jwt, github, postgate, sharedStorage, mistral, anthropic, smtp, appUrl } = config;
163+
export const { nodeEnv, port, jwt, github, postgate, sharedStorage, mistral, anthropic, email, appUrl } = config;

src/routes/workers.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,7 @@ workers.post('/:id/upload', async (c) => {
286286

287287
// 8. Return success with worker URL
288288
const workerDomain = worker.domains?.[0]?.name;
289-
const workerUrl = workerDomain
290-
? `https://${workerDomain}`
291-
: `https://${worker.name}.openworkers.dev`;
289+
const workerUrl = workerDomain ? `https://${workerDomain}` : `https://${worker.name}.workers.rocks`;
292290

293291
return c.json({
294292
success: true,

src/services/email.ts

Lines changed: 66 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,77 @@
1-
import { createTransport, type Transporter } from 'nodemailer';
1+
import { email, appUrl } from '../config';
2+
3+
interface ScalewayEmailRequest {
4+
from: { email: string; name?: string };
5+
to: { email: string; name?: string }[];
6+
subject: string;
7+
text: string;
8+
html: string;
9+
project_id: string;
10+
}
211

3-
import { smtp, appUrl } from '../config';
12+
async function sendWithScaleway(to: string, subject: string, text: string, html: string): Promise<boolean> {
13+
if (!email.secretKey || !email.projectId) {
14+
console.error('[Email] Scaleway not configured (missing SCW_SECRET_KEY or SCW_PROJECT_ID)');
15+
return false;
16+
}
417

5-
let transporter: Transporter | null = null;
18+
const url = `https://api.scaleway.com/transactional-email/v1alpha1/regions/${email.region}/emails`;
619

7-
function getTransporter(): Transporter | null {
8-
if (!smtp.host) {
9-
return null;
10-
}
20+
const body: ScalewayEmailRequest = {
21+
from: { email: email.from },
22+
to: [{ email: to }],
23+
subject,
24+
text,
25+
html,
26+
project_id: email.projectId
27+
};
1128

12-
if (!transporter) {
13-
transporter = createTransport({
14-
host: smtp.host,
15-
port: smtp.port,
16-
secure: smtp.port === 465,
17-
auth: smtp.user && smtp.pass ? {
18-
user: smtp.user,
19-
pass: smtp.pass
20-
} : undefined
29+
try {
30+
const res = await fetch(url, {
31+
method: 'POST',
32+
headers: {
33+
'X-Auth-Token': email.secretKey,
34+
'Content-Type': 'application/json'
35+
},
36+
body: JSON.stringify(body)
2137
});
22-
}
2338

24-
return transporter;
39+
if (!res.ok) {
40+
const error = await res.text();
41+
console.error('[Email] Scaleway API error:', res.status, error);
42+
return false;
43+
}
44+
45+
return true;
46+
} catch (error) {
47+
console.error('[Email] Failed to send email:', error);
48+
return false;
49+
}
2550
}
2651

2752
export function isEmailConfigured(): boolean {
28-
return !!smtp.host;
53+
return email.provider === 'scaleway' && !!email.secretKey && !!email.projectId;
2954
}
3055

31-
export async function sendSetPasswordEmail(email: string, token: string): Promise<boolean> {
32-
const transport = getTransporter();
33-
34-
if (!transport) {
35-
console.log(`[Email] Set password link for ${email}: ${appUrl}/sign-in/set-password?token=${token}`);
56+
export async function sendSetPasswordEmail(emailTo: string, token: string): Promise<boolean> {
57+
if (!isEmailConfigured()) {
58+
console.log(`[Email] Set password link for ${emailTo}: ${appUrl}/sign-in/set-password?token=${token}`);
3659
return true;
3760
}
3861

39-
try {
40-
await transport.sendMail({
41-
from: smtp.from,
42-
to: email,
43-
subject: 'Complete your OpenWorkers registration',
44-
text: `Welcome to OpenWorkers!
62+
const subject = 'Complete your OpenWorkers registration';
63+
64+
const text = `Welcome to OpenWorkers!
4565
4666
Click the link below to set your password and complete your registration:
4767
4868
${appUrl}/sign-in/set-password?token=${token}
4969
5070
This link expires in 24 hours.
5171
52-
If you didn't create an account, you can ignore this email.`,
53-
html: `
72+
If you didn't create an account, you can ignore this email.`;
73+
74+
const html = `
5475
<!DOCTYPE html>
5576
<html>
5677
<head>
@@ -70,39 +91,30 @@ If you didn't create an account, you can ignore this email.`,
7091
<p class="footer">This link expires in 24 hours. If you didn't create an account, you can ignore this email.</p>
7192
</div>
7293
</body>
73-
</html>`
74-
});
94+
</html>`;
7595

76-
return true;
77-
} catch (error) {
78-
console.error('[Email] Failed to send set password email:', error);
79-
return false;
80-
}
96+
return sendWithScaleway(emailTo, subject, text, html);
8197
}
8298

83-
export async function sendPasswordResetEmail(email: string, token: string): Promise<boolean> {
84-
const transport = getTransporter();
85-
86-
if (!transport) {
87-
console.log(`[Email] Password reset link for ${email}: ${appUrl}/sign-in/reset-password?token=${token}`);
99+
export async function sendPasswordResetEmail(emailTo: string, token: string): Promise<boolean> {
100+
if (!isEmailConfigured()) {
101+
console.log(`[Email] Password reset link for ${emailTo}: ${appUrl}/sign-in/reset-password?token=${token}`);
88102
return true;
89103
}
90104

91-
try {
92-
await transport.sendMail({
93-
from: smtp.from,
94-
to: email,
95-
subject: 'Reset your OpenWorkers password',
96-
text: `You requested a password reset for your OpenWorkers account.
105+
const subject = 'Reset your OpenWorkers password';
106+
107+
const text = `You requested a password reset for your OpenWorkers account.
97108
98109
Click the link below to reset your password:
99110
100111
${appUrl}/sign-in/reset-password?token=${token}
101112
102113
This link expires in 1 hour.
103114
104-
If you didn't request this, you can ignore this email.`,
105-
html: `
115+
If you didn't request this, you can ignore this email.`;
116+
117+
const html = `
106118
<!DOCTYPE html>
107119
<html>
108120
<head>
@@ -122,12 +134,7 @@ If you didn't request this, you can ignore this email.`,
122134
<p class="footer">This link expires in 1 hour. If you didn't request this, you can ignore this email.</p>
123135
</div>
124136
</body>
125-
</html>`
126-
});
137+
</html>`;
127138

128-
return true;
129-
} catch (error) {
130-
console.error('[Email] Failed to send password reset email:', error);
131-
return false;
132-
}
139+
return sendWithScaleway(emailTo, subject, text, html);
133140
}

0 commit comments

Comments
 (0)