function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Developer.mikie.Apex.StudentDeveloper.mikie.Apex.Student 

Email Template based on specific record. E.G. Transaction Receipt

Hi there,

I was wondering if it were possible to create a visualforce email template based on a specific record. 

I will explain my situation.

I have account and transactions__c is a custom object which is a child or Account. I was wondering if it is possible to create an email template which basically shows all the fields related to the transaction. Then I could either write a trigger which will send said email template whenever a transaction record is created or use my current email trigger to reference a specific record and then send out an email with the transaction information.

Thank you for your time
Best Answer chosen by Developer.mikie.Apex.Student
CheyneCheyne
Awesome! Yes, there is a way to do that. I would actually map each account ID to a list of contacts, instead of contact IDs. Then you can iterate through all of the contacts, and check if the email is non-null before creating the message.

trigger SendNewTransactionEmail on Transaction__c (after insert) {
    //Get the correct email template here
    EmailTemplate template = [SELECT Id FROM EmailTemplate WHERE Name = 'Your Template Name'];
   
    Set<Id> accountIds = new Set<Id>();
    for (Transaction__c transaction : trigger.new) {
        accountIds.add(transaction.Account__c);
    }
   
    List<Account> accounts = [SELECT Id, (SELECT Id, Email FROM Contacts) FROM Account WHERE Id IN :accountIds];
    Map<Id, List<Contact>> accountMap = new Map<Id, List<Contact>>();
    for (Account acct : accounts) {
        List<Contact> contacts = new List<Contact>();
        for (Contact c : acct.Contacts) {
            contacts.add(c);
        }
        accountMap.put(acct.Id, contacts);
    }

    //It's best to make a list of emails to send, so that you can send them all at once at the end, in order to avoid hitting governor limits
    List<Messaging.SingleEmailMessage> messages = new List<Messaging.SingleEmailMessage>();
   
    for (Transaction__c transaction : trigger.new) {
        for (Contact c : accountMap.get(transaction.Account__c) {
            if (c.Email != null) {
                Messaging.SingleEmailMessage m = new Messaging.SingleEmailMessage();
                m.setTemplateId(template.Id);
                m.setTargetObjectId(c.Id);
                m.setWhatId(transaction.Id);
                messages.add(m);
            }   
        }
    }
    Messaging.sendEmail(messages);
}

All Answers

CheyneCheyne
You should be able to use a Visualforce email template to do this. With that sort of email template, you can set the Recipient type, which could be a Contact, and also set a Related To type, which, in this case, would be the Transactions__c. Then you can reference fields from both objects in the template.

If you're sending from an Apex trigger you can use the setTargetObjectId() and setWhatId() methods on SingleEmailMessage to specify the two records that you are using.
Developer.mikie.Apex.StudentDeveloper.mikie.Apex.Student
Would you perhaps be able to elaborate on how I can reference this. Is it perhaps possible to send an email referencing the record using a custom save function extension on a new record page?
CheyneCheyne
I believe it could be as simple as creating a workflow rule on new Transactions, as long as the transaction is related to a contact. If it's only related to an account,as you said, then you may want to write a trigger in order to use more advanced logic to find the correct contact to link to the template. I don't know what your data model looks like, so I'm not sure what the best way of pulling in the correct contact would be. The trigger would look something like this:

trigger SendNewTransactionEmail on Transaction__c (after insert) {
    //Get the correct email template here
    EmailTemplate template = [SELECT Id FROM EmailTemplate WHERE Name = 'Your Template Name'];
   
    //It's best to make a list of emails to send, so that you can send them all at once at the end, in order to avoid hitting governor limits
    List<Messaging.SingleEmailMessage> messages = new List<Messaging.SingleEmailMessage>();

    for (Transaction__c transaction : trigger.new) {
        //You could add some logic here to only send the email only for certain transactions, if necessary
        Messaging.SingleEmailMessage m = new Messaging.SingleEmailMessage();
        m.setTemplateId(template.Id);

        //You'll need to somehow figure out the correct contact ID here
        m.setTargetObjectId(contactId);

        m.setWhatId(transaction.Id);
        messages.add(m);
    }
    Messaging.sendEmail(messages);
}
Developer.mikie.Apex.StudentDeveloper.mikie.Apex.Student
Because we are using lists are we still able to send a singleemailmessage to all contacts associated with the account?
CheyneCheyne
Sure, you'll just need to get a list of all of the relevant contacts. I would do it by making a map which maps each account ID to its list of contacts. Putting it all together, your code might look something like this:

trigger SendNewTransactionEmail on Transaction__c (after insert) {
    //Get the correct email template here
    EmailTemplate template = [SELECT Id FROM EmailTemplate WHERE Name = 'Your Template Name'];
    
    Set<Id> accountIds = new Set<Id>();
    for (Transaction__c transaction : trigger.new) {
        accountIds.add(transaction.Account__c);
    }
    
    List<Account> accounts = [SELECT Id, (SELECT Id FROM Contacts) FROM Account WHERE Id IN :accountIds];
    Map<Id, List<Id>> accountMap = new Map<Id, List<Id>>();
    for (Account acct : accounts) {
        List<Id> contactIds = new List<Id>();
        for (Contact c : acct.Contacts) {
            contactIds.add(c.Id);
        }
        accountMap.put(acct.Id, contactIds);
    }

    //It's best to make a list of emails to send, so that you can send them all at once at the end, in order to avoid hitting governor limits
    List<Messaging.SingleEmailMessage> messages = new List<Messaging.SingleEmailMessage>();
    
    for (Transaction__c transaction : trigger.new) {
        for (Contact c : accountMap.get(transaction.Account__c) {
            Messaging.SingleEmailMessage m = new Messaging.SingleEmailMessage();
            m.setTemplateId(template.Id);
            m.setTargetObjectId(c.Id);
            m.setWhatId(transaction.Id);
            messages.add(m);    
        }
    }
    Messaging.sendEmail(messages);
}
CheyneCheyne
Sorry, I made a mistake. accountMap maps account IDs to lists of contact IDs, not contacts, so that last for loop should actually be written like this:

for (Transaction__c transaction : trigger.new) {
        for (Id contactId : accountMap.get(transaction.Account__c) {
            Messaging.SingleEmailMessage m = new Messaging.SingleEmailMessage();
            m.setTemplateId(template.Id);
            m.setTargetObjectId(contactId);
            m.setWhatId(transaction.Id);
            messages.add(m);   
        }
    }
Developer.mikie.Apex.StudentDeveloper.mikie.Apex.Student

thank you so much, it is working. I just had a quick question. Before I mark your solution as correct. 

 

the trigger seems to fail, should one of the contacts not have an email address. Is there anyway around this?

CheyneCheyne
Awesome! Yes, there is a way to do that. I would actually map each account ID to a list of contacts, instead of contact IDs. Then you can iterate through all of the contacts, and check if the email is non-null before creating the message.

trigger SendNewTransactionEmail on Transaction__c (after insert) {
    //Get the correct email template here
    EmailTemplate template = [SELECT Id FROM EmailTemplate WHERE Name = 'Your Template Name'];
   
    Set<Id> accountIds = new Set<Id>();
    for (Transaction__c transaction : trigger.new) {
        accountIds.add(transaction.Account__c);
    }
   
    List<Account> accounts = [SELECT Id, (SELECT Id, Email FROM Contacts) FROM Account WHERE Id IN :accountIds];
    Map<Id, List<Contact>> accountMap = new Map<Id, List<Contact>>();
    for (Account acct : accounts) {
        List<Contact> contacts = new List<Contact>();
        for (Contact c : acct.Contacts) {
            contacts.add(c);
        }
        accountMap.put(acct.Id, contacts);
    }

    //It's best to make a list of emails to send, so that you can send them all at once at the end, in order to avoid hitting governor limits
    List<Messaging.SingleEmailMessage> messages = new List<Messaging.SingleEmailMessage>();
   
    for (Transaction__c transaction : trigger.new) {
        for (Contact c : accountMap.get(transaction.Account__c) {
            if (c.Email != null) {
                Messaging.SingleEmailMessage m = new Messaging.SingleEmailMessage();
                m.setTemplateId(template.Id);
                m.setTargetObjectId(c.Id);
                m.setWhatId(transaction.Id);
                messages.add(m);
            }   
        }
    }
    Messaging.sendEmail(messages);
}
This was selected as the best answer
Developer.mikie.Apex.StudentDeveloper.mikie.Apex.Student

Thank you so much for your time. You have been an amazing help! I could not have done it without you. I will probably swap my other email code for this!

 

For all future thread viewers, should cheynes code error slightly the email just has to be querries in the SOQL. End code as follows:

 

trigger SendNewTransactionEmail on Transaction__c (after insert) {
    //Get the correct email template here
    EmailTemplate template = [SELECT Id FROM EmailTemplate WHERE Name = 'Transaction_Receipt'];
    
    Set<Id> accountIds = new Set<Id>();
    for (Transaction__c tra : trigger.new) {
        accountIds.add(tra.Account__c);
    }
    
    List<Account> accounts = [SELECT Id, (SELECT Id, Email FROM Contacts) FROM Account WHERE Id IN :accountIds];
    Map<Id, List<Contact>> accountMap = new Map<Id, List<Contact>>();
    for (Account acct : accounts) {
        List<Contact> contacts = new List<Contact>();
        for (Contact c : acct.Contacts) {
            contacts.add(c);
        }
        accountMap.put(acct.Id, contacts);
    }

    //It's best to make a list of emails to send, so that you can send them all at once at the end, in order to avoid hitting governor limits
    List<Messaging.SingleEmailMessage> messages = new List<Messaging.SingleEmailMessage>();
    
   for (Transaction__c tra : trigger.new) {
         for (Contact c : accountMap.get(tra.Account__c)) {
         if (c.Email != null) {
            Messaging.SingleEmailMessage m = new Messaging.SingleEmailMessage();
            m.setTemplateId(template.Id);
            m.setTargetObjectId(c.Id);
            m.setWhatId(tra.Id);
            messages.add(m);   
        }
    }
    }
    Messaging.sendEmail(messages);
}