Ferdinand
Ferdinand5mo ago

fetch request on internal action

I am trying to send an email using nodemailer and gmail. I created an api in next js api app router. export const newUserEmail = internalAction({ args: { email: v.string(), name: v.string() }, handler: async (ctx, args) => { const newUser = await fetch('http://localhost:3000/api/email/newUser', { method: 'POST', body: JSON.stringify({ email: args.email, name: args.name }), }); console.log('convex email', newUser); return newUser; }, }); I am getting this error from convex log: Uncaught Error: Request to http://localhost:3000/api/email/deleteUser forbidden. Please how can I resolve this.
6 Replies
erquhart
erquhart5mo ago
Your convex functions run in the cloud, they don't have access to your localhost.
Michal Srb
Michal Srb5mo ago
Have you tried using nodemailer directly via "use node" Convex action?
ian
ian5mo ago
ngrok & tunnelmole are proxies to expose localhost to a url to hit from the cloud while testing. but agree that you shouldn't need api endpoints outside of Convex - no need to go to a different server to run code if you don't have to.
Ferdinand
FerdinandOP5mo ago
Yes, but I am getting this error:
Uncaught Error: Missing credentials for "PLAIN"
at _formatError [as _formatError] (../node_modules/nodemailer/lib/smtp-connection/index.js:798:8)
at login [as login] (../node_modules/nodemailer/lib/smtp-connection/index.js:452:12)
at <anonymous> (../node_modules/nodemailer/lib/smtp-transport/index.js:272:28)
at SMTPConnection.<anonymous> (../node_modules/nodemailer/lib/smtp-connection/index.js:215:13)
at _actionEHLO [as _actionEHLO] (../node_modules/nodemailer/lib/smtp-connection/index.js:1360:4)
at _processResponse [as _processResponse] (../node_modules/nodemailer/lib/smtp-connection/index.js:982:12)
at _onData [as _onData] (../node_modules/nodemailer/lib/smtp-connection/index.js:763:4)
at SMTPConnection._onSocketData (../node_modules/nodemailer/lib/smtp-connection/index.js:195:44)
Uncaught Error: Missing credentials for "PLAIN"
at _formatError [as _formatError] (../node_modules/nodemailer/lib/smtp-connection/index.js:798:8)
at login [as login] (../node_modules/nodemailer/lib/smtp-connection/index.js:452:12)
at <anonymous> (../node_modules/nodemailer/lib/smtp-transport/index.js:272:28)
at SMTPConnection.<anonymous> (../node_modules/nodemailer/lib/smtp-connection/index.js:215:13)
at _actionEHLO [as _actionEHLO] (../node_modules/nodemailer/lib/smtp-connection/index.js:1360:4)
at _processResponse [as _processResponse] (../node_modules/nodemailer/lib/smtp-connection/index.js:982:12)
at _onData [as _onData] (../node_modules/nodemailer/lib/smtp-connection/index.js:763:4)
at SMTPConnection._onSocketData (../node_modules/nodemailer/lib/smtp-connection/index.js:195:44)
This is my code:
'use node';

import { v } from 'convex/values';
import { action, internalAction } from './_generated/server';
import * as nodemailer from 'nodemailer';
import { MailOptions } from 'nodemailer/lib/json-transport';

export class Emailer {
private readonly transporter: nodemailer.Transporter;

constructor() {
this.transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.GMAIL_USER,
pass: process.env.GMAIL_PASSWORD,
},
});
}

public async sendEmail(mailOptions: MailOptions) {
return await this.transporter.sendMail(mailOptions);
}

public async notifyAdminForNewUser(email: string, username: string) {
return await this.sendEmail(
notifyAdminNewUserEmailTemplate(email, username)
);
}

public async notifyUserForSignup(email: string, username: string) {
return await this.sendEmail(newUserEmailTemplate(email, username));
}

public async notifyUserForDeletedAccount(
email: string | null | undefined,
username: string | null | undefined
) {
return await this.sendEmail(deleteUserEmailTemplate(email, username));
}

public async contactAdmin(
username: string,
email: string,
phone: string,
message: string
) {
return await this.sendEmail(
contactAdminEmailTemplate(username, email, phone, message)
);
}
}

export const emailer = new Emailer();
'use node';

import { v } from 'convex/values';
import { action, internalAction } from './_generated/server';
import * as nodemailer from 'nodemailer';
import { MailOptions } from 'nodemailer/lib/json-transport';

export class Emailer {
private readonly transporter: nodemailer.Transporter;

constructor() {
this.transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.GMAIL_USER,
pass: process.env.GMAIL_PASSWORD,
},
});
}

public async sendEmail(mailOptions: MailOptions) {
return await this.transporter.sendMail(mailOptions);
}

public async notifyAdminForNewUser(email: string, username: string) {
return await this.sendEmail(
notifyAdminNewUserEmailTemplate(email, username)
);
}

public async notifyUserForSignup(email: string, username: string) {
return await this.sendEmail(newUserEmailTemplate(email, username));
}

public async notifyUserForDeletedAccount(
email: string | null | undefined,
username: string | null | undefined
) {
return await this.sendEmail(deleteUserEmailTemplate(email, username));
}

public async contactAdmin(
username: string,
email: string,
phone: string,
message: string
) {
return await this.sendEmail(
contactAdminEmailTemplate(username, email, phone, message)
);
}
}

export const emailer = new Emailer();
export const newUserEmailTemplate = (email: string, username: string) => {
return {
from: {
name: 'Boulot Sur',
address: process.env.GMAIL_USER,
},
to: email,
subject: `${username}, Welcome to Boulot Sur`,
text: 'Welcome to Boulot Sur',
html: `
<h1>Welcome to BOULOT SUR!</h1>
<p>We're glad you've decided to join us. We hope you find everything you're looking for here and enjoy using our site.</p>
<p>If you have any questions or need any help, please don't hesitate to contact us. Thank you for signing up!</p>
`,
} as MailOptions;
};

export const deleteUserEmailTemplate = (
email: string | null | undefined,
username: string | null | undefined
) => {
return {
from: {
name: 'Boulot Sur',
address: process.env.GMAIL_USER,
},
to: email,
subject: `${username}, Boulot Sur: Account deleted`,
text: 'Boulot Sur: Account deleted',
html: `
<h1>Account Deleted!</h1>
<p>We're sorry you've decided to leave us. We are looking forward to see you next time.</p>
<p>If you have any questions or need any help, please don't hesitate to contact us. Thank you for using our services!</p>
`,
} as MailOptions;
};
export const newUserEmailTemplate = (email: string, username: string) => {
return {
from: {
name: 'Boulot Sur',
address: process.env.GMAIL_USER,
},
to: email,
subject: `${username}, Welcome to Boulot Sur`,
text: 'Welcome to Boulot Sur',
html: `
<h1>Welcome to BOULOT SUR!</h1>
<p>We're glad you've decided to join us. We hope you find everything you're looking for here and enjoy using our site.</p>
<p>If you have any questions or need any help, please don't hesitate to contact us. Thank you for signing up!</p>
`,
} as MailOptions;
};

export const deleteUserEmailTemplate = (
email: string | null | undefined,
username: string | null | undefined
) => {
return {
from: {
name: 'Boulot Sur',
address: process.env.GMAIL_USER,
},
to: email,
subject: `${username}, Boulot Sur: Account deleted`,
text: 'Boulot Sur: Account deleted',
html: `
<h1>Account Deleted!</h1>
<p>We're sorry you've decided to leave us. We are looking forward to see you next time.</p>
<p>If you have any questions or need any help, please don't hesitate to contact us. Thank you for using our services!</p>
`,
} as MailOptions;
};
export const newUserEmail = action({
args: { email: v.string(), name: v.string() },
handler: async (ctx, args) => {
const newUser = await emailer.notifyAdminForNewUser(
args.email,
args.name
);

console.log('convex email new User', newUser);
return newUser;
},
});

export const deleteUserEmail = action({
args: { email: v.string(), name: v.string() },
handler: async (ctx, args) => {
const deletedUser = await emailer.notifyUserForDeletedAccount(
args.email,
args.name
);
console.log('convex email delete User', deletedUser);
return deletedUser;
},
});
export const newUserEmail = action({
args: { email: v.string(), name: v.string() },
handler: async (ctx, args) => {
const newUser = await emailer.notifyAdminForNewUser(
args.email,
args.name
);

console.log('convex email new User', newUser);
return newUser;
},
});

export const deleteUserEmail = action({
args: { email: v.string(), name: v.string() },
handler: async (ctx, args) => {
const deletedUser = await emailer.notifyUserForDeletedAccount(
args.email,
args.name
);
console.log('convex email delete User', deletedUser);
return deletedUser;
},
});
Michal Srb
Michal Srb5mo ago
Missing credentials for "PLAIN"
Do you have all relevant environment variables set on your Convex dashboard? https://docs.convex.dev/production/environment-variables
Environment Variables | Convex Developer Hub
Store and access environment variables in Convex
Ferdinand
FerdinandOP5mo ago
Thanks much it works perfectly

Did you find this page helpful?