I can't send emails - Firebase CloudFunctions - node.js

I'm new to Firebase Cloud Functions and I want to create a mailing system on it. I need to receive an email in my account (for example myemail#gmail.com) every time a new data is inserted in my Realtime Database.The email must contain the contents of the firebase node.
I tried to use this code, but when adding new data to the Realtime database, I don't get any emails:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const nodemailer = require('nodemailer');
const gmailEmail = functions.config().gmail.email;
const gmailPassword = functions.config().gmail.password;
const mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'email#gmail.com',
pass: 'password'
},
});
admin.initializeApp();
exports.sendEmail = functions.database.ref('/reports/{postId}/{reportId}').onWrite(async (change) => {
const snapshot = change.after;
const val = snapshot.val();
const mailOptions = {
from: '"Report" <email#gmail.com>',
to: 'myemail#gmail.com',
};
// Building Email message.
mailOptions.subject = 'Report ' + val.tipo;
mailOptions.text = 'Content ' + val;
try {
await mailTransport.sendMail(mailOptions);
console.log('email sent! ');
} catch(error) {
console.error('There was an error while sending the email:', error);
}
return null;
});
Database:
"reports" : {
"-M2sV-8jze4u8di8rC6a" : {
"-M2smZT3YgGe6pbHb9ML" : {
"idAuthor" : "-M2sV-8jze4u8di8rC6a",
"idReport" : "-M2smZT3YgGe6pbHb9ML",
"tipo" : "test"
},
How can I do to send emails with the contents of the database?
Thanks in advance!

Related

I want to store the verification Code from Cognito in database and to send the email later

exports.handler = async event => {
console.log(event);
try {
const { userName } = event;
const { codeParameter } = event.request;
let userParams = { userId: userName, codeParameter };
// Identify why was this function invoked
if (event.triggerSource === 'CustomMessage_ForgotPassword') {
userParams['type'] = customCognitoTrigger.FORGOT_PASSWORD;
await putItemInTable(userParams, cognitoCodesTable);
}
if (event.triggerSource === 'CustomMessage_SignUp') {
userParams['type'] = customCognitoTrigger.SIGN_UP;
await putItemInTable(userParams, cognitoCodesTable);
}
} catch (err) {
console.log(err);
if (err.statusCode) {
return utils.returnEndpointMessage(err.statusCode, {
message: err.code || err.message,
});
}
return utils.returnSomethingWentWrongMessage();
}
// Return to Amazon Cognito
return event;
};
How can I stop the cognito base Email? I'm still receiving a base email with my verification code. I just want to store it in database without receiving any email.
{
version: '1',
region: 'eu-west-1',
userPoolId: 'xxxxxxx',
userName: 'xxxxxxxx',
callerContext: {
awsSdkVersion: 'aws-sdk-unknown-unknown',
clientId: 'xxxxxxxx'
},
triggerSource: 'CustomMessage_SignUp',
request: {
userAttributes: {
sub: 'xxxxxxx',
'cognito:email_alias': 'xxxxxxx',
email_verified: 'false',
'cognito:user_status': 'UNCONFIRMED',
email: 'xxxxxxx'
},
codeParameter: '{####}',
linkParameter: '{##Click Here##}',
usernameParameter: null
},
response: { smsMessage: null, emailMessage: null, emailSubject: null }
}
That's the event, should I change something in response?
In order to stop Cognito from sending the emails, you need to configure a Custom email sender Lambda trigger (note that, as stated in documentation, this trigger is not assignable from the AWS Console as other lambdas, you'll need to use the API to assign it). The Custom message Lambda trigger you are using only allows customization of messages sent by AWS.
Here's a snippet example, based on documentation, on how it can be done to have access to the decoded verification code and then store it in the DB:
const AWS = require('aws-sdk');
const b64 = require('base64-js');
const encryptionSdk = require('#aws-crypto/client-node');
const { encrypt, decrypt } = encryptionSdk.buildClient(encryptionSdk.CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT);
const generatorKeyId = process.env.KEY_ALIAS;
const keyIds = [ process.env.KEY_ARN ];
const keyring = new encryptionSdk.KmsKeyringNode({ generatorKeyId, keyIds });
exports.handler = async (event) => {
// Decrypt the secret code using encryption SDK.
let plainTextCode;
if (event.request.code) {
const { plaintext, messageHeader } = await decrypt(keyring, b64.toByteArray(event.request.code));
plainTextCode = plaintext
// TODO: Store the plainTextCode to Database ...
}
// additional code ...
return;
};

Sending emails to registered users worldwide by 12am at users specific timezone

I am currently working on a project and right now I need to send emails to users worldwide on different time zone exactly by 12am.
I'm using express.js for my backend server( temporally deployed my server to heroku), MongoDB for database management , alongside node-cron for scheduling the emails with nodemailer and mailgun. Pls guys any Ideas on how my registered users can all receive their emails exactly by 12am at their different time zone.
my code base
const nodemailer = require("nodemailer");
const mg = require("nodemailer-mailgun-transport");
const handlebars = require("handlebars");
const fs = require("fs");
const path = require("path");
let cron = require("node-cron");
const { User } = require("../models/user");
const verses = require("kjv/json/verses-1769.json");
const store = require("store2");
const { bible_books } = require("./data");
const _ = require("lodash");
async function getUsersEmail() {
const users = await User.find().sort("email").select("email");
return users;
}
module.exports = async function () {
const emailTemplateSource = fs.readFileSync(
path.join(__dirname, "../views/template.hbs"),
"utf8"
);
const mailgunAuth = {
auth: {
api_key: process.env.mailgun_apikey,
domain: process.env.mailgun_domain,
},
};
const smtpTransport = nodemailer.createTransport(mg(mailgunAuth));
const template = handlebars.compile(emailTemplateSource);
cron.schedule('0 0 * * *', async function () {
console.log("running task every day at 12:00 am");
(await getUsersEmail()).map(async function (value) {
const bible_book = _.sampleSize(bible_books, 1);
store("bible", bible_book[0]);
let verse = store("bible");
let bible_passage = verse;
let bible_text = verses[bible_passage];
await User.findByIdAndUpdate(
value?._id,
{
bible: {
verse: bible_passage,
text: bible_text,
},
},
{ new: true }
);
const htmlToSend = template({
verse: bible_passage,
text: bible_text,
imageUrl:
"https://myimage.com/logo.png",
redirectUrl: "https://mywebsite.org/",
year: new Date().getFullYear(),
});
const mailOptions = {
from: "from#sender.org",
to: value?.email,
subject: "Mail subject",
html: htmlToSend,
};
smtpTransport.sendMail(
mailOptions,
function (error, response) {
if (error) {
console.log(error);
} else {
console.log(`Successfully sent email to ${mailOptions.to}.`);
}
}
);
});
});
}

Domain-wide delegation using default credentials in Google Cloud Run

I'm using a custom service account (using --service-account parameter in the deploy command). That service account has domain-wide delegation enabled and it's installed in the G Apps Admin panel.
I tried this code:
app.get('/test', async (req, res) => {
const auth = new google.auth.GoogleAuth()
const gmailClient = google.gmail({ version: 'v1' })
const { data } = await gmailClient.users.labels.list({ auth, userId: 'user#domain.com' })
return res.json(data).end()
})
It works if I run it on my machine (having the GOOGLE_APPLICATION_CREDENTIALS env var setted to the path of the same service account that is assigned to the Cloud Run service) but when it's running in Cloud Run, I get this response:
{
"code" : 400,
"errors" : [ {
"domain" : "global",
"message" : "Bad Request",
"reason" : "failedPrecondition"
} ],
"message" : "Bad Request"
}
I saw this solution for this same issue, but it's for Python and I don't know how to replicate that behaviour with the Node library.
After some days of research, I finally got a working solution (porting the Python implementation):
async function getGoogleCredentials(subject: string, scopes: string[]): Promise<JWT | OAuth2Client> {
const auth = new google.auth.GoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
})
const authClient = await auth.getClient()
if (authClient instanceof JWT) {
return (await new google.auth.GoogleAuth({ scopes, clientOptions: { subject } }).getClient()) as JWT
} else if (authClient instanceof Compute) {
const serviceAccountEmail = (await auth.getCredentials()).client_email
const unpaddedB64encode = (input: string) =>
Buffer.from(input)
.toString('base64')
.replace(/=*$/, '')
const now = Math.floor(new Date().getTime() / 1000)
const expiry = now + 3600
const payload = JSON.stringify({
aud: 'https://accounts.google.com/o/oauth2/token',
exp: expiry,
iat: now,
iss: serviceAccountEmail,
scope: scopes.join(' '),
sub: subject,
})
const header = JSON.stringify({
alg: 'RS256',
typ: 'JWT',
})
const iamPayload = `${unpaddedB64encode(header)}.${unpaddedB64encode(payload)}`
const iam = google.iam('v1')
const { data } = await iam.projects.serviceAccounts.signBlob({
auth: authClient,
name: `projects/-/serviceAccounts/${serviceAccountEmail}`,
requestBody: {
bytesToSign: unpaddedB64encode(iamPayload),
},
})
const assertion = `${iamPayload}.${data.signature!.replace(/=*$/, '')}`
const headers = { 'content-type': 'application/x-www-form-urlencoded' }
const body = querystring.encode({ assertion, grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer' })
const response = await fetch('https://accounts.google.com/o/oauth2/token', { method: 'POST', headers, body }).then(r => r.json())
const newCredentials = new OAuth2Client()
newCredentials.setCredentials({ access_token: response.access_token })
return newCredentials
} else {
throw new Error('Unexpected authentication type')
}
}
What you can do here is define ENV variables in your yaml file as described in this documentation to set the GOOGLE_APPLICATION_CREDENTIALS to the path of the JSON key.
Then use a code such as the one mentioned here.
const authCloudExplicit = async ({projectId, keyFilename}) => {
// [START auth_cloud_explicit]
// Imports the Google Cloud client library.
const {Storage} = require('#google-cloud/storage');
// Instantiates a client. Explicitly use service account credentials by
// specifying the private key file. All clients in google-cloud-node have this
// helper, see https://github.com/GoogleCloudPlatform/google-cloud-node/blob/master/docs/authentication.md
// const projectId = 'project-id'
// const keyFilename = '/path/to/keyfile.json'
const storage = new Storage({projectId, keyFilename});
// Makes an authenticated API request.
try {
const [buckets] = await storage.getBuckets();
console.log('Buckets:');
buckets.forEach(bucket => {
console.log(bucket.name);
});
} catch (err) {
console.error('ERROR:', err);
}
// [END auth_cloud_explicit]
};
Or follow an approach similar to the one mentioned here.
'use strict';
const {auth, Compute} = require('google-auth-library');
async function main() {
const client = new Compute({
serviceAccountEmail: 'some-service-account#example.com',
});
const projectId = await auth.getProjectId();
const url = `https://dns.googleapis.com/dns/v1/projects/${projectId}`;
const res = await client.request({url});
console.log(res.data);
}
main().catch(console.error);

How to send email in Firebase for free?

I'm aware that Firebase doesn't allow you to send emails using 3rd party email services. So the only way is to send through Gmail.
So I searched the internet for ways, so here's a snippet that works and allows me to send email without cost.
export const shareSpeechWithEmail = functions.firestore
.document("/sharedSpeeches/{userId}")
.onCreate(async (snapshot, context) => {
// const userId = context.params.userId;
// const data = snapshot.data();
const mailTransport = nodemailer.createTransport(
`smtps://${process.env.USER_EMAIL}:${process.env.USER_PASSWORD}#smtp.gmail.com`
);
const mailOptions = {
to: "test#gmail.com",
subject: `Message test`,
html: `<p><b>test</b></p>`
};
try {
return mailTransport.sendMail(mailOptions);
} catch (err) {
console.log(err);
return Promise.reject(err);
}
});
I want to create a template, so I used this package called email-templates for nodemailer.
But the function doesn't get executed in Firebase Console and it doesn't show an error and shows a warning related to "billing".
export const shareSpeechWithEmail = functions.firestore
.document("/sharedSpeeches/{userId}")
.onCreate(async (snapshot, context) => {
const email = new Email({
send: true,
preview: false,
views: {
root: path.resolve(__dirname, "../../src/emails")
// root: path.resolve(__dirname, "emails")
},
message: {
// from: "<noreply#domain.com>"
from: process.env.USER_EMAIL
},
transport: {
secure: false,
host: "smtp.gmail.com",
port: 465,
auth: {
user: process.env.USER_EMAIL,
pass: process.env.USER_PASSWORD
}
}
});
try {
return email.send({
template: "sharedSpeech",
message: {
to: "test#gmail.com",
subject: "message test"
},
locals: {
toUser: "testuser1",
fromUser: "testuser2",
title: "Speech 1",
body: "<p>test using email <b>templates</b></p>"
}
});
} catch (err) {
console.log(err);
return Promise.reject(err);
}
});
You can definitely send emails using third party services and Cloud Functions, as long as your project is on the Blaze plan. The official provided samples even suggest that "if switching to Sendgrid, Mailjet or Mailgun make sure you enable billing on your Firebase project as this is required to send requests to non-Google services."
https://github.com/firebase/functions-samples/tree/master/quickstarts/email-users
The key here, no matter which email system you're using, is that you really need to upgrade to the Blaze plan in order to make outgoing connections.
you can send emails by using nodemailer:
npm install nodemailer cors
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const nodemailer = require('nodemailer');
const cors = require('cors')({origin: true});
admin.initializeApp();
/**
* Here we're using Gmail to send
*/
let transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'yourgmailaccount#gmail.com',
pass: 'yourgmailaccpassword'
}
});
exports.sendMail = functions.https.onRequest((req, res) => {
cors(req, res, () => {
// getting dest email by query string
const dest = req.query.dest;
const mailOptions = {
from: 'Your Account Name <yourgmailaccount#gmail.com>', // Something like: Jane Doe <janedoe#gmail.com>
to: dest,
subject: 'test', // email subject
html: `<p style="font-size: 16px;">test it!!</p>
<br />
` // email content in HTML
};
// returning result
return transporter.sendMail(mailOptions, (erro, info) => {
if(erro){
return res.send(erro.toString());
}
return res.send('Sended');
});
});
});
See also here
Set Security-Level to avoid error-messages:
Go to : https://www.google.com/settings/security/lesssecureapps
set the Access for less secure apps setting to Enable
Refer to
Call a sendMail() cloud function directly via functions.https.onCall(..) :
As #Micha mentions don't forget to enable Less Secure Apps for the outgoing email: https://www.google.com/settings/security/lesssecureapps
const functions = require('firebase-functions');
const nodemailer = require('nodemailer');
let mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'supportabc#gmail.com',
pass: '11112222'
}
});
exports.sendMail = functions.https.onCall((data, context) => {
console.log('enter exports.sendMail, data: ' + JSON.stringify(data));
const recipientEmail = data['recipientEmail'];
console.log('recipientEmail: ' + recipientEmail);
const mailOptions = {
from: 'Abc Support <Abc_Support#gmail.com>',
to: recipientEmail,
html:
`<p style="font-size: 16px;">Thanks for signing up</p>
<p style="font-size: 12px;">Stay tuned for more updates soon</p>
<p style="font-size: 12px;">Best Regards,</p>
<p style="font-size: 12px;">-Support Team</p>
` // email content in HTML
};
mailOptions.subject = 'Welcome to Abc';
return mailTransport.sendMail(mailOptions).then(() => {
console.log('email sent to:', recipientEmail);
return new Promise(((resolve, reject) => {
return resolve({
result: 'email sent to: ' + recipientEmail
});
}));
});
});
Thanks also to: Micha's post
You can send for free with Firebase extensions and Sendgrid:
https://medium.com/firebase-developers/firebase-extension-trigger-email-5802800bb9ea
DEPRECATED: https://www.google.com/settings/security/lesssecureapps
Use "Sign in with App Passwords": https://support.google.com/accounts/answer/185833?hl=en to create an app password.
follow the steps in the link then use the given password with your user in mailTransport:
const mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'user#gmail.com',
pass: 'bunchofletterspassword'
}
});

How to send arrays to email using angular and firestore

I'm trying to send array data from firestore database using sendgrid.
I have tried using the documentation to no luck.
export const newOrder = functions.firestore
.document("checkout/{checkoutId}/products/{productId}")
.onCreate(async (change, context) => {
// Read booking document
const postSnap = await db
.collection("checkout/{checkoutId}/products")
.doc(context.params.productId)
.get();
const booking = postSnap.data() || {};
//Email
const msg = {
to: "wilmutsami#gmail.com",
from: "test#example.com",
templateId: TEMPLATE_ID,
dynamic_template_data: {
subject: "Hey there, thank you for your order!",
name: [booking.name],
amount: [booking.amount]
}
};
//Send it
return sgMail.send(msg);
});

Resources