I am using a template to send an email. I want to send the email to multiple recipients, but in the field email_to accept only object attributes:
<field name="email_to">${object.attribute}</field>
I want to send email using the same template to multiple recipients.
I am using below code:
def action_send_email_to_attendees(self, cr, uid, ids, context=None):
'''
This function opens a window to compose an email
'''
assert len(ids) == 1, 'This option should only be used for a single id at a time.'
ir_model_data = self.pool.get('ir.model.data')
try:
template_id = ir_model_data.get_object_reference(cr, uid, 'mymodule', 'mymodule_invitation_email')[1]
except ValueError:
template_id = False
try:
compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
except ValueError:
compose_form_id = False
ctx = dict(context)
ctx.update({
'default_model': 'mymodule.module',
'default_res_id': ids[0],
'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
'mark_so_as_sent': True
})
return {
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
'views': [(compose_form_id, 'form')],
'view_id': compose_form_id,
'target': 'new',
'context': ctx,
}
Related
I'm trying to redirect to view account.view_move_form after invoicing several POS orders and merging those invoices into one, but after the action is done it stays at POS order tree view. Here is my code:
def invoice_and_merge(self):
partner_id = self[0].partner_id
account_moves = []
account_moves_ids = []
wizard = {}
for pedido in self:
if pedido.partner_id != partner_id:
raise UserError('Todas las facturas debes ser del mismo cliente')
#Invoice each order at a time and add to array
account_moves.append(pedido.action_pos_order_invoice())
for move in account_moves:
account_moves_ids.append(move['res_id'])
factura = self.env['account.move'].search([('id', '=', move['res_id'])])
# Set state of created invoices to Draft so they can be merged
factura.button_draft()
wizard['merge_type'] = 'cancel'
wizard['partner_id'] = partner_id.id
merge_invoice = self.env['sh.minv.merge.invoice.wizard'].with_context({'active_ids': account_moves_ids}).create(wizard)
# This is a third party app that merges serveral invoices into a new one and returns a tree view with all new invoices
ret = merge_invoice.action_merge_invoice()
facturas = self.env['account.move'].search([('id','in', ret['domain'][0][2])])
id = 0
for factura in facturas:
if factura.name == '/':
self.account_move = factura
id = factura.id
# Set state of merged invoice to Posted
factura.action_post()
else:
factura.write({'agrupado': True})
# Return form view with the new invoice
return {
'name': _('Customer Invoice'),
'view_mode': 'form',
'view_id': self.env.ref('account.view_move_form').id,
'res_model': 'account.move',
'context': "{'move_type':'out_invoice'}",
'type': 'ir.actions.act_window',
'nodestroy': True,
'res_id': id,
'target': 'current'
}
I'm using nodemailer for email submission and running from my localhost. I have email services created manually in the following dir /api/email/services/Email.js
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: 'example.com',
port: 587,
secure: false,
auth: {
user: 'user',
pass: 'password',
},
});
module.exports = {
send: (from, to, subject, html) => {
const options = {
from,
to,
subject,
html
};
return transporter.sendMail(options);
},
};
So then I can use it like strapi.services.email.send(from, email, subject, html);
Usually, I write my html template in the code line const html = '<p>Email testing</p>' to be passed in the email services. But I don't want to do this for every email submission from different controllers.
So, I created a html template in /config/email-templates/custom-email.html and tried to call it like const html = path.join(__dirname + '/../../../config/email-templates/custom-email.html');.
When I run it, the email can be sent successfully but it cannot render the html. Instead of a rendered html, it's showing the full path of the custom-email.html as the email message. Is this method possible to achieve in strapi?
Instead of passing the path to the file, you need to pass the actual content. In the first case const html = '<p>Email testing</p>' , you are actually passing the content , but in the second case you are passing the file path.
Modified send method could look something like below:
send: (from, to, subject, htmlpath) => {
const readHTMLFile = (path, callback)=> {
fs.readFile(path, {encoding: "utf-8"}, function (err, html) {
if (err)
return callback(err);
else
return callback(null, html);
});
}
readHTMLFile(htmlpath, function(err, html) {
const options = {
from,
to,
subject,
html
};
return transporter.sendMail(options);
}); }
I tried:
client.lists.members.create('1111111', {
'email_address' : 'frogger116#gmail.com',
'status' : 'subscribed',
"tags": [{'name': 'frogger', 'status' : 'active'}],
})
and get:
mailchimp3.mailchimpclient.MailChimpError: {
'type': 'http://developer.mailchimp.com/documentation/mailchimp/guides/error-glossary/',
'title': 'Invalid Resource',
'status': 400,
'detail': 'Expected argument of type "string", "stdClass" given',
'instance': '5ae1b966-ed35-49f1-ad32-0a39c3d63593'
}
Without the "tags" line the satement works
This works for me (you need hash the email address)
hash = get_subscriber_hash(email_address)
data = {
'tags': [
{'name': 'foo', 'status': 'active'},
{'name': 'bar', 'status': 'inactive'}
]
}
client.lists.members.tags.update(list_id=list_id, subscriber_hash=hash, data=data)
I think in the latest version the tags are just strings. At least, this is what I use.
client.lists.members.create('1111111', {
'email_address' : 'frogger116#gmail.com',
'status' : 'subscribed',
'tags': ['frogger','invaders']
})
Also, to be certain, make sure tags exist in the system. Especially if doing an update rather than insert. Its not super consistant about dealing with unknown tags.
Also of course, make sure the '1111111' audience id exists.
SUPPLEMENTAL CODE
Of course, I've found a better way to ensure what you want is to use other methods to add to tags rather than on insert. You may have to tweak the below functions as they are part of a class, but they should give you other ideas on how to add and remove tags from users
def bulkUpdateTagNamesForEmails(self, emailsToUpdate, tagNames, remove=False):
audienceId = self.audienceId
if remove:
data = {'members_to_remove':emailsToUpdate}
else:
data = {'members_to_add':emailsToUpdate}
segments = None
for tagName in tagNames:
segId,segments = self.getSegmentIdFromTagName(tagName,segments)
if segId:
self.brResponse = self.mcClient.lists.segments.update_members(list_id=audienceId, segment_id=segId, data=data)
def createTagNameIfNeeded(self, tagName):
audienceId = self.audienceId
# Check for tag name
found = False
segments = self.mcClient.lists.segments.all(list_id=audienceId, get_all=False)
for segment in segments['segments']:
if segment['name'] == tagName:
found = True
print("Found tag")
# If not found, create it
if not found:
print(f"Creating new tag {tagName}")
data = {'name': tagName,'static_segment': []}
self.mcClient.lists.segments.create(list_id=audienceId, data=data)
def getSegmentIdFromTagName(self,reqTagName,segments=None):
audienceId = self.audienceId
reqId = None
if not segments:
segments = self.mcClient.lists.segments.all(list_id=audienceId, get_all=True)
for segment in segments['segments']:
segName = segment['name']
segId = segment['id']
if segName == reqTagName:
reqId = segId
break
return reqId,segments
I want to add an avatar in the user registration, but I don't know how, Please can someone share with me a full example (form, JS front, and JS backend). I'm using SailsJS 1.0 (the stable version) with VueJs.
Thanks in advance .
I figured it out. Watch these platzi tutorials:
https://courses.platzi.com/classes/1273-sails-js/10757-uploading-backend-file/
https://courses.platzi.com/classes/1273-sails-js/10758-uploading-frontend-files/
https://courses.platzi.com/classes/1273-sails-js/10759-downloading-files/
Here is what the videos tell you to do:
npm i sails-hook-uploads.
In api/controllers/entrance/signup.js
Above inputs key add a new key/value of files: ['avatar'],
In the inputs add:
avatar: {
type: 'ref',
required: true
}
In the body of the fn find var newUserRecord and above this add (even if avatar is not required, make sure to do this line, otherwise you will have a "timeout of unconsuemd file stream":
const avatarInfo = await sails.uploadOne(inputs.avatar);
Then in the first argument object of var newUserRecord = await User.create(_.extend({ add:
avatarFd: avatarInfo.fd,
avatarMime: avatarInfo.type
In api/models/User.js, add these attributes to your User model:
avatarFd: {
type: 'string',
required: false,
description: 'will either have "text" or "avatarFd"'
},
avatarMime: {
type: 'string',
required: false,
description: 'required if "avatarFd" provided'
},
Then create a download endpoint, here is how the action would look for it:
const user = await User.findOne(id);
this.res.type(paste.photoMime);
const avatarStream = await sails.startDownload(paste.photoFd);
return exits.success(avatarStream);
Add to the routes a route for this download avatar endpoint.
Then you can display this avatar by pointing the <img src=""> the source in here to this download endpoint.
------APPENDIX-----
----signup.js-----
module.exports = {
friendlyName: 'Signup',
description: 'Sign up for a new user account.',
extendedDescription:
`This creates a new user record in the database, signs in the requesting user agent
by modifying its [session](https://sailsjs.com/documentation/concepts/sessions), and
(if emailing with Mailgun is enabled) sends an account verification email.
If a verification email is sent, the new user's account is put in an "unconfirmed" state
until they confirm they are using a legitimate email address (by clicking the link in
the account verification message.)`,
files: ['avatar'],
inputs: {
emailAddress: {
required: true,
type: 'string',
isEmail: true,
description: 'The email address for the new account, e.g. m#example.com.',
extendedDescription: 'Must be a valid email address.',
},
password: {
required: true,
type: 'string',
maxLength: 200,
example: 'passwordlol',
description: 'The unencrypted password to use for the new account.'
},
fullName: {
required: true,
type: 'string',
example: 'Frida Kahlo de Rivera',
description: 'The user\'s full name.',
},
avatar: {
}
},
exits: {
success: {
description: 'New user account was created successfully.'
},
invalid: {
responseType: 'badRequest',
description: 'The provided fullName, password and/or email address are invalid.',
extendedDescription: 'If this request was sent from a graphical user interface, the request '+
'parameters should have been validated/coerced _before_ they were sent.'
},
emailAlreadyInUse: {
statusCode: 409,
description: 'The provided email address is already in use.',
},
},
fn: async function (inputs) {
var newEmailAddress = inputs.emailAddress.toLowerCase();
// must do this even if inputs.avatar is not required
const avatarInfo = await sails.uploadOne(inputs.avatar);
// Build up data for the new user record and save it to the database.
// (Also use `fetch` to retrieve the new ID so that we can use it below.)
var newUserRecord = await User.create(_.extend({
emailAddress: newEmailAddress,
password: await sails.helpers.passwords.hashPassword(inputs.password),
fullName: inputs.fullName,
tosAcceptedByIp: this.req.ip,
avatarFd: avatarInfo.fd,
avatarMime: avatarInfo.type
}, sails.config.custom.verifyEmailAddresses? {
emailProofToken: await sails.helpers.strings.random('url-friendly'),
emailProofTokenExpiresAt: Date.now() + sails.config.custom.emailProofTokenTTL,
emailStatus: 'unconfirmed'
}:{}))
.intercept('E_UNIQUE', 'emailAlreadyInUse')
.intercept({name: 'UsageError'}, 'invalid')
.fetch();
// If billing feaures are enabled, save a new customer entry in the Stripe API.
// Then persist the Stripe customer id in the database.
if (sails.config.custom.enableBillingFeatures) {
let stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
emailAddress: newEmailAddress
}).timeout(5000).retry();
await User.updateOne(newUserRecord.id)
.set({
stripeCustomerId
});
}
// Store the user's new id in their session.
this.req.session.userId = newUserRecord.id;
if (sails.config.custom.verifyEmailAddresses) {
// Send "confirm account" email
await sails.helpers.sendTemplateEmail.with({
to: newEmailAddress,
subject: 'Please confirm your account',
template: 'email-verify-account',
templateData: {
fullName: inputs.fullName,
token: newUserRecord.emailProofToken
}
});
} else {
sails.log.info('Skipping new account email verification... (since `verifyEmailAddresses` is disabled)');
}
// add to pubilc group
const publicGroup = await Group.fetchPublicGroup();
await Group.addMember(publicGroup.id, newUserRecord.id);
}
};
I'm in the process of creating a mass mailer that allows a company to send out an email template to a specific plan that contains X amount of subscribers.
However, when using pg-promise, I can't figure how to loop through an array that contains one or more unique plan names that'll then be used as query parameters for subsequent queries to gather a single array of results. The subsequent queries don't resolve before express sends back a specified error message.
For example:
const selectedTemplate = await db.oneOrNone("SELECT * FROM templates WHERE userid=$1 and uniqueTemplateName=$2", [req.session.id, req.body.uniquetemplatename])
returns a JSON object...
{ ...
plans : [ 'Unique Plan 1', 'Unique Plan 2', 'Unique Plan 3'...],
...
}
I then want to loop through this plans array to gather some subscriber emails. Since the selectedTemplate's plans array can contain a single "Unique Plan" or multiple, I'm trying to figure out how to get this query to work:
let subscriberEmails = [];
await each(selectedTemplate.plans, async plan => {
const emails = await db.any("SELECT email FROM subscribers WHERE userid=$1 AND planName=$2", [req.session.id, plan])
each(emails, ({email}) => subscriberEmails.push(email))
console.log('Found emails: ', subscriberEmails);
})
console.log('Selected emails: ', subscriberEmails);
res.status(500).json({ message: "Route not configured yet" })
The problem I'm having is that Express isn't waiting for the pg-promise to resolve. So it's triggering my 500 server "Route not configured yet" message, then resolves the queries:
14:48:33 SELECT * FROM templates WHERE userid='12345' and uniqueTemplateName='example-unique-template'
selectedTemplate: anonymous {
id: '84efa448-b149-11e8-a7fd-3b9e4e9e5ece',
key: 9,
userid: '12345',
status: 'active',
fromsender: 'example#helpdesk.com',
subject: 'Thank you for using our services!',
templatename: 'Example Unique Template',
uniquetemplatename: 'example-unique-template',
message: '<h3>This is a test!</h3>',
plans: [ 'Unique Plan 1', 'Unique Plan 2' ]
}
Selected emails: []
POST /api/messages/create 500 28 - 34.125 ms
14:48:33 SELECT email FROM subscribers WHERE userid='12345' AND planName='Unique Plan 1'
Found emails: [
'betatester19#example.com',
'betatester20#example.com',
'betatester21#example.com'
]
14:48:34 SELECT email FROM subscribers WHERE userid='12345' AND planName='Unique Plan 2'
Found emails: [
'betatester19#example.com',
'betatester20#example.com',
'betatester21#example.com',
'betatester1#example.com',
'betatester2#example.com',
'betatester3#example.com',
'betatester4#example.com',
'betatester5#example.com',
'betatester6#example.com',
'betatester7#example.com',
'betatester8#example.com',
'betatester9#example.com',
'betatester10#example.com',
'betatester11#example.com',
'betatester12#example.com',
'betatester13#example.com',
'betatester14#example.com',
'betatester15#example.com',
'betatester16#example.com',
'betatester17#example.com',
'betatester18#example.com'
]
The function as is:
async (req, res, done) => {
if (!req.body) {
return res.status(404).json({ message: "Missing query parameters" });
done();
}
try {
const selectedTemplate = await db.oneOrNone("SELECT * FROM templates WHERE userid=$1 and uniqueTemplateName=$2", [req.session.id, uniquetemplatename]);
if (!selectedTemplate) {
return res.status(404).json({ message: "Unable to locate selected template" });
done();
}
console.log('selectedTemplate', selectedTemplate);
let subscriberEmails = [];
await each(selectedTemplate.plans, async plan => {
const emails = await db.any("SELECT email FROM subscribers WHERE userid=$1 AND planName=$2", [req.session.id, plan])
each(emails, ({email}) => subscriberEmails.push(email))
console.log('Found emails: ', subscriberEmails);
})
console.log('Selected emails: ', subscriberEmails);
res.status(500).json({ message: "Route not configured yet" })
} catch (err) { res.status(500).json({ message: err }); done(); }
}
A vastly simplified DB structure:
├── Template
│ └── Plans (references an array of unique plan name(s))
|
|── Plan
| └── PlanName (unique plan name)
|
│── Subscriber
| └── PlanName (references a single unique plan name)
| └── Email (unique email)
I've tried using a db.task(), but again, the queries didn't resolve before Express sent back the message.
Figured out where the problem was. Just in case anyone else stumbles upon this predicament: Functions like "map" and "each" don't handle async/await. They instead just return a pending Promise. Therefore, you'll want to use a library like Bluebird, which implements Promise-based each/map/etc functions (found under "Collections").
The fix for the above to work:
try {
await db.task('create-message', async t => {
const selectedTemplate = await t.oneOrNone(findTemplateByName, [req.session.id, uniquetemplatename])
if (!selectedTemplate) return sendError(unableToLocate('template'), res, done);
const { fromsender, subject, message, plans, templatename } = selectedTemplate;
let subscriberEmails = [];
await Promise.each(plans, async plan => {
const emails = await t.any(getAllEmailsByPlan, [req.session.id, plan]);
each(emails, ({email}) => subscriberEmails.push(email));
})
if (isEmpty(subscriberEmails)) return sendError(unableToLocate('subscribers in the selected plan(s)'), res, done);
const msg = {
to: subscriberEmails,
from: fromsender,
replyTo: fromsender,
subject: subject,
html: message
};
await mailer.sendMultiple(msg);
await t.none(createMessageTransaction, [req.session.id, templatename, fromsender, subject, currentDate, plans]);
await t.none(createNotification, [req.session.id, 'mail_outline', `The following template: ${templatename} has been sent out to the subscibers in the following ${plans.length > 1 ? "plans" : "plan"}: ${plans}.`, date]);
return res.status(201).send(null);
})
} catch (err) { return sendError(err, res, done); }