I\'m trying to clean up some of the the email views in my grails project. My team uses the same introduction, logo, and sign off for every email. I tried to place these into the
I was trying to figure out what you mean by DRY, I think it must be some ruby term and I guess you mean templates.
The problem with HTML emails is actually a standard problem acrosss all languages as in when it comes to including logos (headers/footers). The standards vary and whilst it may work on some mail clients may not work on for example web mail i.e. gmail and so forth.
The trick is to use inline images I will give you some sample stuff to work from:
So here is a sample Controller - this is off my own code but from different parts. It is to give you an idea or should I say not very well explained specifically around multi inline images:
class Mycontroller() {
pivate final static String TEMPLATE='/emails/emailTemplate'
def emailService
def doEmail() {
def images=[]
//Where this list contains a map of photo Ids(could be macde up, the photo content type and actual photo file Names (to go and grab from uploaded folder)
images <<[id: "uImage${photo.id}", contentType: "${photo.contentType}", file: photo.file]
images <<[id: "uImage${photo1.id}", contentType: "${photo1.contentType}", file: photo1.file]
emailService.sendEmail(user.email, subject, TEMPLATE, [instance: bean, domainName:domainName, fqdn:fqdn ], images)
}
}
I have a list if images above it containts the photo id the content type and the actual file name (in text) that is sent through to emailService.sendEmail
private void sendEmail(email,mysubject,template,templateModel,List images) throws Exception {
List recipients = []
try {
mailService.sendMail {
//this must be set true and at the top for inline images to work
multipart true
if (recipients) {
to recipients
}
else {
to email
}
if (config.admin.emailFrom) {
if (Environment.current == Environment.DEVELOPMENT && config.admin.emailFromDev ) {
from "${config.admin.emailFromDev}"
} else {
from "${config.admin.emailFrom}"
}
}
subject mysubject
//actual content must be html sent to fill in a grails template
html Holders.grailsApplication.mainContext.groovyPageRenderer.render(template: template, model: templateModel)
//Main Site logo
inline 'inlineImage', 'image/png', new File("/opt/site-stuff/myLogo.png")
//Additional images
if (images) {
images?.each { a ->
inline "${a.id}", "${a.contentType}", new File("${a.file}")
}
}
}
}
catch (e) {
//throw new Exception(e.message)
log.error "Problem sending email ${e.message}"
}
}
Now the bit that you thought would just work as in using grails templates in the way you are as in layouts is not what you want instead, as above you can see it is rendering a template but the template is a typical gsp template which instead is a full on html page:
/emails/emailTemplate
<%@ page contentType="text/html;charset=UTF-8" %>
You see the main problem with html emails is that CSS styles don't work very well, it works in some cases but in a lot of cases you are better off sticking to traditional tables and using style tags to properly declare your layout.
Forget about using your actual site CSS files since so far as this process goes, this is a direct email being generated and sent. It has no awareness of your site CSS files.
In regards to multiple images we are talking about
here is a list of users
usera {usera Photo} / userA Description userb {userb Photo} / userB Description
The above solution will have all you need to add all the inline images you need to do this. This means you are attaching the images within the email so if the images are huge then you are also attaching them so the trick is to reformat the images / resize them.
For your own site logos you can do that directly and have a separate file/folder that contains actual size to be emailed but for on the fly re-sizing you could try something like this:
static Map getPhoto(Photos photo, int width=100,int height=100) {
File f
def contentType
if (photo.status==Photos.ACTIVE) {
def id = photo.id
def imageSHa = photo.imageSHa
contentType = photo.contentType
def fileExtension = photo.fileExtension
//remove . from fileExtension
def noDotExtension = fileExtension.substring(1)
def user = photo.user
f = new File(ROOT_PATH + '/' + user.username + '/' + imageSHa);
if (f.exists() && !f.isDirectory()) {
f = new File(ROOT_PATH + '/' + user.username + '/' + imageSHa+'_email');
if (!f.exists()) {
def imageStream = new FileInputStream(ROOT_PATH + '/' + user.username + '/' + imageSHa)
def image = FileCopyUtils.copyToByteArray(imageStream).encodeBase64().toString()
def caption = photo.caption
def position = photo.position
// String caption=photo.caption
//Lets present the image as a thumbNail
imageStream = new FileInputStream(ROOT_PATH + '/' + user.username + '/' + imageSHa)
def imageBuffer = ImageIO.read(imageStream)
def scaledImg = Scalr.resize(imageBuffer, Scalr.Method.QUALITY, width, height, Scalr.OP_ANTIALIAS)
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(scaledImg, noDotExtension, os)
InputStream is = new ByteArrayInputStream(os.toByteArray())
def scaledImage = FileCopyUtils.copyToByteArray(is).encodeBase64().toString()
//imageSHa = DigestUtils.shaHex(scaledImg)
byte[] data = Base64.decodeBase64(scaledImage)
OutputStream stream = new FileOutputStream(ROOT_PATH + '/' + user.username + '/' + imageSHa+'_email')
stream.write(data)
f = new File(ROOT_PATH + '/' + user.username + '/' + imageSHa+'_email');
}
return [file:f, contentType:'img/'+fileExtension.substring(1)]
}
}
return [:]
}
This now maps up to when I was doing the images mapping above:
def res = PhotosBean.getPhoto(ui.attributes.profilePhoto)
if (res) {
images << [id: "uImage${ui.id}", contentType: "${res.contentType}", file: res.file]
}
Hope this clears up a lot of headache I had to go through to achieve html emails with as many images as required and all resized to what I want