Salesforce Developer Apex Basics & OOPs Concepts
Salesforce Developer Apex Basics & OOPs Concepts
What is Apex?
Apex is a strongly typed, object-oriented programming language
that is designed specifically for the Salesforce platform.
It allows developers to create custom functionality, such as
triggers, controllers for Visualforce pages, batch processes,
workflows, and more, to extend and enhance the standard
capabilities of Salesforce applications.
Key Features of Apex?
1. Salesforce Setup
2. Developer Console
3. Execute Anonymous window
4. Visual Studio Code
My First APEX Program
System.debug(‘Cloud Analogy’);
Escape characters (\’ , \n, \t)
Date A value that indicates a particular day. Unlike Datetime values, Date
values contain no information about time. Date Class
Datetime A value that indicates a particular day and time, such as a timestamp.
Date Time Class
Integer A 32-bit number that doesn’t include a decimal point. Integers have
a minimum value of -2,147,483,648 and a maximum value of
2,147,483,647.
Long A 64-bit number that doesn’t include a decimal point. Longs have a
minimum value of -263 and a maximum value of 263-1
Primitive Data Types
Data Type Description
Object Any data type that is supported in Apex. Apex supports primitive data
types (such as Integer), user-defined custom classes, the sObject
generic type, or an sObject specific type (such as Account). All Apex
data types inherit from Object.
You can cast an object that represents a more specific data type to
its underlying data type.
Object obj = 10;
Integer i = (Integer)obj;
capitalize() Returns the current String with the first letter str.capitalize();
changed to title case
contains(substring) Returns true if and only if the String that called myString1.contains(my
the method contains the specified sequence of String2);
characters in substring.
➢Lists hold an ordered collection of objects. Lists in Apex are synonymous with
arrays and the two can be used interchangeably.
➢The following two declarations are equivalent. The colors variable is declared
using the List syntax.
· List<String> colors = new List<String>();
➢ Alternatively, the colors variable can be declared as an array but assigned to a
list rather than an array.
· String[] colors = new List<String>();
equals(list2) Compares this list with the specified list and myList.equals(newList)
returns true if both lists are equal; otherwise,
returns false.
set(index, Sets the specified value for the element at the myList.set(2,10)
listElement) given index.
Syntax
Set <datatype> setVar = new Set<datatype>{value1,value2,...value n};
containsAll(list Returns true if the set contains all of the elements in myList.containsAll(
ToCompare) the specified list. The list must be of the same type newList)
as the set that calls the method.
containsAll(set Returns true if the set contains all of the elements in myList.containsAll(
ToCompare) the specified set. The specified set must be of the newSet)
same type as the original set that calls the method.
Set Methods
retainAll(listOfEle Retains only the elements in this set that are mySet.retainAll(myLi
mentsToRetain) contained in the specified list. st)
Set Methods
equals(map2) Compares this map with the specified map and myMap.equals(n
returns true if both maps are equal; otherwise, ewMap)
returns false.
keySet() Returns a set that contains all of the keys in the myMap.keySet()
map.
Map
Or
Account acc;
// assignment operator
Integer x = 5;
x = x + 5; x = x * 5;
x = x - 5; x = x/5;
x += 5; x *= 5;
x -= 5; x /= 5;
Operators
// using addition operator on strings
//decrement operator
//increment operator
X - - ; //
x++;
System.debug('decrement operator - '+x);
System.debug('increment operator - '+x);
Operators
// Ternary operator
➢In this case, the (Integer) syntax explicitly casts the Decimal value myDeci
to an Integer.
Type Conversion Methods
➢String to Integer: Use Integer.valueOf(String).
Integer.valueOf('65675665')
➢String to Decimal: Use Decimal.valueOf(String).
Decimal.valueOf('656756.65')
➢Integer to String: Use String.valueOf(Integer) or Integer.toString().
String str = String.valueOf(54665455);
➢Decimal to String: Use String.valueOf(Decimal) or Decimal.toString().
String str = String.valueOf(6576577.87);
➢Date/DateTime to String: Use String.valueOf(Date).
Date myDate = Date.valueOf('2024-05-24');
String str = String.valueOf(myDate);
Type conversion Examples
if ([Boolean_condition]) if (place == 1)
// Statement 1 { medal_color = 'gold'; }
else // Statement 2 else if (place == 2)
{ medal_color = 'silver’; }
else if (place == 3)
{ medal_color = 'bronze'; }
else { medal_color = null; }
Control Flow Statements
➢Switch Statements: Apex provides a switch statement that tests whether an
expression matches one of several values and branches accordingly.
switch on expression {
when value1 {
// when block 1
}
when value2 {
// when block 2
}
when value3 {
// when block 3
}
when else {
// default block, optional
}
}
Control Flow Statements
When Blocks:
➢Each when block has a value that the expression is matched against.
These values can take one of the following forms:
➢when literal {}
➢when SObjectType identifier {}
➢when enum_value {}
➢The value null is a legal value for all types
Control Flow Statements
Examples with Literals Null Value Example Multiple Values Examples
switch on i { switch on i { switch on i {
when 2 { when 2 { when 2, 3, 4 {
System.debug('when block 2'); System.debug('when block System.debug('when block 2 and
} 2'); 3 and 4');
when -3 { } }
System.debug('when block -3'); when null { when 5, 6 {
} System.debug('bad System.debug('when block 5 and
when else { integer'); 6');
System.debug('default'); } }
} when else { when 7 {
} System.debug('default ' + System.debug('when block 7');
i); }
} when else {
} System.debug('default');
}
}
Control Flow Statements
Method Example Example with sObjects Example with Enums
Integer count = 1;
do { System.debug(count);
count++;
} while (count < 11);
Loop Statements
While Loops
while(condition)
{
code_block
}
Integer count = 1;
while (count < 11) {
System.debug(count);
count++;
}
Loop Statements
}
system.debug(s);
*
} **
} ***
****
} *****
Script for Pattern
public class Pattern1 {
public static void P1(integer n){
for(integer i=1;i<=n;i++)
{
string s=' ';
for(integer j=i;j<=n;j++)
{
s= s+'x';
}
system.debug(s);
*****
****
} ***
}
**
} *
Script for Pattern
public class Pattern1 {
public static void P1(){
for(integer i=1;i<=5;i++){
string s=' ';
boolean k=true;
for(integer j=1;j<=9;j++) {
if(j>=i && j<=10-i && k){
s = s + 'x';
k=false;
}
else{ *****
s = s + '_'; ****
k=true; ***
}
}
**
system.debug(s); *
}
}
Classes & Objects in Apex
pieces of data (fields), and behaviours (methods) related to data into special
➢ Object - Any real world entity is an object and every object have state (fields)
➢ Class - Class is a blueprint which defines the structure for the objects.
Apex Class
➢ Objects Properties
➢ Classes Name
RollNumber
Age
○ Properties
○ Behaviour
Behaviour
markAttendance()
getAttendance()
payFee()
Apex Class
public class HelloWorld { /*To execute above class, use below code in
String greeting = 'Hello World'; anonymous window of dev console*/
➢ Private
➢ Protected
➢ Public
➢ Global
Access Modifiers in Apex
Private:
Protected:
➢ Visibility: only in class in which it is declared and the class that extends this
class
➢ Inner classes and classes that extend outer class can access these variables
and methods
Public:
➢ Visibility: If a class is public then its content is visible to all classes in the org.
Global:
➢ No Difference in Public and Global if the classes are in the same org. But, if
class A is public and class B is Global and both the classes are in a package in
AppExchange and you are using this package in your org then content of class
A will not be visible but content of class B will be visible in your org.
}
Abstract Class vs Interface
Abstract Class Interface
Abstract class can have abstract and non-abstract Interface can have only abstract methods.
methods.
Abstract class doesn't support multiple inheritance. Interface supports multiple inheritance.
Abstract class can provide the implementation of Interface can't provide the implementation of
The abstract keyword is used to declare abstract The interface keyword is used to declare interface.
class.
Inheritance
➢ Capability to create a new class which will contain functionality of parent
class
➢ You can change existing method
➢ You can add some more method without impacting parent class methods
➢ Parent class and methods to be overridden must be declared virtual/abstract.
➢ Child class must use extends keyword
➢ Extending class can override the existing virtual/abstract methods by using
the override keyword in the method definition.
Virtual & Abstract Class
➢ Both virtual and abstract classes allow you to extend the class (i.e. create
child classes that inherit non-private methods and variables).
➢ Methods in virtual class CAN be overridden in derived classes, while
abstract class methods MUST be overridden.
➢ A virtual class can be instantiated directly, whereas an abstract class
cannot.
➢ Both virtual and abstract classes can contain virtual methods (virtual
methods can have a default implementation that is inherited by child
classes, whereas abstract methods can only be signatures, and must be
implemented in child classes).
Inheritance
Example1:
}
Inheritance Important Points
➢ A class that extends another class inherits all the methods and properties of the
extended class. In addition, the extending class can override the existing virtual
methods by using the override keyword in the method definition.
➢ A class extends another class using the extends keyword in the class definition.
➢ A class can only extend one other class, but it can implement more than one interface.
➢ Use the virtual keyword, only classes with virtual can be extended
➢ Extending class can override and provide a new definition to the virtual method
Inheritance Task
Create a school application with a class called Person. Create name and
dateOfBirth as member variables.
Create a class called Teacher that inherits from the Person class. The teacher will
have additional properties like salary, and the subject that the teacher teaches.
Create a class called Student that inherits from Person class. This class will have
a member variable called studentId.
Create a class called College Student that inherits from Student class. This class
will have collegeName, the year in which the student is studying
(first/second/third/fourth) etc.
Create objects of each of these classes, invoke and test the methods that are
available in these classes.
Polymorphic Methods
Example 2:
}
Static Variable
➢ Static variable belongs to class and not to instance of it.
➢ So only one copy of a static variable is created and shared among various
instances while a separate normal variable is created for each instance variable.
public class covid19{
NumberOfPatientInCity++;
NumberOfPatientInCountry++;
}
Static Variable
Noida.patientNew();
Delhi.patientNew();
Instance methods, member variables, and initialization code have these characteristics.
➢ Static final variables can be changed in static initialization code or where defined.
➢ Non-final static variables are used to communicate state at the class level (such as
state between triggers). However, they are not shared across requests.
➢ Methods and classes are final by default. You cannot use the final keyword in the
declaration of a class or method. This means they cannot be overridden. Use the virtual
keyword if you need to override a method or class.
Using instanceof Keyword
➢ Use instanceof keyword to verify at
run time whether an object is actually
In Apex saved with API version 32.0 and later,
an instance of a particular class.
instanceof returns false if the left operand is a null
if (Reports.get(0) instanceof CustomReport) { object. For example, the following sample returns false.
// Can safely cast it back to a custom report object
CustomReport c = (CustomReport) Reports.get(0); Object obj = null;
} else {
Boolean result = obj instanceof Account;
// Do something with the non-custom-report.
System.assertEquals(false, result);
}
➢ We can use the this keyword in dot notation, without parenthesis, to represent the
current instance of the class in which it appears. Use this form of the this keyword to
access instance variables and methods.
➢ We can use the this keyword to do constructor chaining, that is, in one constructor,
call another constructor.
Using this Keyword
public class TrainingDemo {
String name;
Integer age;
public TrainingDemo(String name, Integer age){
this.name = name;
this.age = age;
}
public void display(){
System.debug('The name is '+this.name+' and age is '+this.age);
}
}
Using this Keyword
Use 2: Use this keyword to do constructor chaining, that is, in one constructor, call another constructor. In this
format, use the this keyword with parentheses. For example:
// This constructor calls the first constructor using the this keyword.
public testThis() {
Note: When you use the this keyword in
this('None');
a constructor to do constructor
} chaining, it must be the first statement
in the constructor.
}
super in Apex
➢ The super keyword can be used by classes that are extended from virtual or abstract
classes.
➢ By using super, we can override constructors and methods from the parent class.
Using super to Access Constructor
➢ Super can be used to call the constructor of the parent class.
// Here we are using getter to get the public class Student extends Person{
values
String studentId;
public virtual class Person { public Student(String studentId,
String name; String name, Integer age){
Integer age; super(name, age);
public Person(String name, Integer this.studentId = studentId;
age){ }
this.name = name; public void
this.age = age; displayStudentInfo(){
} System.debug('Student Id is:
public String getName(){ '+this.studentId+' name is:
return this.name; '+this.getName()+' age is:
} '+this.getAge());
public Integer getAge(){ }
return this.age; }
}
}
Using super to Access Constructor
➢ If you don’t want to access it through getter then make the property as public
➢ Instance of inner class can not be created without creating instance of outer
class.
Inner Apex Class or Wrapper Class
public class Company {
public String companyName;
public String ceo;
public Integer employeeCount;
public Long revenue;
lnx.getAllCustomers();
Annotations
➢ An apex annotation modifies the way that a class/method or variable is used.
➢ Annotations are defined with an initial @ symbol, followed by the appropriate
keyword.
global class MyClass {
@deprecated
@future //Used for Asynchronous Apex
public void game-2010()
Public static void myMethod(String a)
{
{
System.debug(‘this is older version’);
//long-running Apex code
}
}
}
➢ An exception denotes an error or event that disrupts the normal flow of code
execution.
➢ Exception disrupts the normal flow of the application hence to keep the
Built-In Exceptions
try{
Account accRec = new Account();
insert accRec;
System.debug('Empty try block ');
} catch(Exception e){
System.debug('Caught generic exception ');
} finally {
System.debug('finally called ');
}
Exception Methods
getMessage String Returns the error message that displays for the user.
setMessage Void Sets the error message that displays for the user.
Exception Methods
Example: try{
Account accRec = new Account();
insert accRec;
System.debug('Empty try block ');
} catch(Exception e){
system.debug('Cause '+e.getCause());
system.debug('Message '+e.getMessage());
system.debug('Line Number '+e.getLineNumber());
system.debug('Stack Trace '+e.getStackTraceString ());
} finally {
System.debug('finally called ');
}
Create Custom Exception
➢ We can create custom exception class by Extending the Exception class.
➢ Custom exception class should end with word Exception.
public class NewException extends Exception {
}
public class Person {
public void canVote(Integer age){
try{
if(age<18){
throw new NewException();
}else{
System.debug('You can vote');
}
}catch(NewException e){
System.debug(e.getTypeName());
}finally{
System.debug('This is finally block'); }
}
}
Assertion Methods
➢ Use Assert method to prove that your code is working fine.
assert(condition, msg)
➢ Asserts that the specified condition is true.
➢ If it is not, a fatal error is returned that causes code execution to halt.
assertEquals(expected, actual, msg)
➢ Asserts that the first two arguments are the same.
➢ If they are not, a fatal error is returned that causes code execution to halt.lt.
➢ Prevent Regression.
What to Test in Apex
➢ Single action
○ Test to verify that a single record produces the correct, expected result.
➢ Bulk actions
○ Any Apex code, whether a trigger, a class or an extension, may be invoked for 1 to 200 records. You must test
not only the single record case, but the bulk cases as well.
➢ Positive behavior
○ Test to verify that the expected behavior occurs through every expected permutation, that is, that the user
filled out everything correctly and did not go past the limits.
➢ Negative behavior
○ There are likely limits to your applications, such as not being able to add a future date, not being able to
specify a negative amount, and so on. You must test for the negative case and verify that the error messages
are correctly produced as well as for the positive, within the limits cases.
➢ Restricted user
○ Test whether a user with restricted access to the sObjects used in your code sees the expected behavior.
That is, whether they can run the code or receive error messages.
Apex Unit Tests
➢ To facilitate the development of robust, error-free code, Apex supports the
creation and execution of unit tests.
➢ Unit tests are class methods that verify whether a particular piece of code is
working properly
➢ Unit test methods take no arguments, commit no data to the database, and
send no emails.
➢ Such methods are flagged with the @isTest annotation in the method
definition.
➢ Unit test methods must be defined in test classes, that is, classes annotated
with @isTest.
Test Class Syntax
@isTest
@isTest
public class MyClass
public class MyClass
{
{
static testMethod void myTest()
@isTest
{
public static void myTest()
//code
{
}
//code
}
}
}
➢ Test methods can not be used to test Web service callouts. (This is the reason why
required code coverage is not 100%). Please use callout mock .
➢ Since test methods don’t commit data created in a test, you don't have to delete test data
upon completion.
➢ Test method and test classes are not counted as a part of code limit.
Structure of a Test
➢ Run Code
➢ Assert Results
What is Test data
➢ Test data is the transient data that is not committed to the database and is created by
each test class to test the Apex code functionality for which it is created.
➢ Use of this transient test data makes it easy to test the functionality of other Apex
classes and triggers.
➢ This does not change the data already in the system, nor any cleanup required after
testing has been finished.
➢ Test classes can be moved to any org and executed there as they create their own data;
hence, they are org agnostic.
How to create Test Data
➢ Test setup
➢ Test Factory
Testing Apex
//Make a class to be tested
public void display(){
public class Covid19{
System.debug('Number of Cases in city: '+this.NumberOfPatientInCity);
public Integer NumberOfPatientInCity = 0;
System.debug('Number of Cases in Country:
public static Integer NumberOfPatientInCountry = 0;
'+NumberOfPatientInCountry);
}
public Covid19(Integer numberOfPatient){
}
this.NumberOfPatientInCity = numberOfPatient;
//Make a test class for above class
NumberOfPatientInCountry += numberOfPatient;
@isTest
}
public class Covid19Test {
@isTest
public void incrasePatient(){
public static void incrasePatientTest(){
NumberOfPatientInCity++;
Covid19 noida = new Covid19(5);
NumberOfPatientInCountry++;
Integer num = noida.localPatient();
}
System.assertEquals(5, num, 'num does not match');
public Integer localPatient(){
}
return NumberOfPatientInCity;
}
}
@isTest
@isTest @isTest
public class AccountTriggerHandlerTest { private static void copyBillingToShippingUpdateTest(){
@isTest List<Account> accList = new List<Account>();
private static void copyBillingToShippingInsertTest(){ for(Integer i=0; i<2; i++){
List<Account> accList = new List<Account>(); Account acc = new Account();
for(Integer i=0; i<2; i++){ acc.Name = 'Test acc'+ i;
Account acc = new Account(); acc.BillingCity = 'Test city'+i;
acc.Name = 'Test acc'+ i; acc.BillingCountry = 'Test Country'+i;
acc.BillingCity = 'Test city'+i; acc.BillingState = 'Test State'+i;
acc.BillingCountry = 'Test Country'+i; acc.BillingPostalCode = '201310';
acc.BillingState = 'Test State'+i; acc.BillingStreet = 'Test street'+i;
acc.BillingPostalCode = '201310'; accList.add(acc); }
acc.BillingStreet = 'Test street'+i; Test.startTest();
acc.Copy_Billing_To_Shipping__c = true; insert accList;
accList.add(acc); } for(Integer i=0; i<2; i++){
Test.startTest(); accList[i].Copy_Billing_To_Shipping__c = true; }
insert accList; update accList;
Test.stopTest(); Test.stopTest();
Account acc = [SELECT Id, ShippingCity FROM Account Account acc = [SELECT Id, ShippingCity FROM Account WHERE Id =
WHERE Id = :accList[0].Id]; :accList[0].Id];
System.assertEquals(accList[0].BillingCity, System.assertEquals(accList[0].BillingCity, acc.ShippingCity);
acc.ShippingCity); } }}
@TestVisible Annotation
Use the @TestVisible annotation to allow test methods to access private or protected members of another class outside
the test class
}
@testSetup Annotation
In setup method we are creating the data and in method1 we are changing the data. But when we are asserting the data in
method2 then it is not changed.
@isTest @isTest
public class TestSetupApex { private static void method1(){
//The data that will be created in this method can not Account acc1 = [SELECT Id,Name, Phone FROM Account
be changed by other methods WHERE Name='Test0'];
@testSetup acc1.Phone = '987675777';
private static void setup(){ update acc1;
List<Account> accList = new List<Account>(); Account acc2 = [SELECT Id,Name, Phone FROM Account
for(Integer i=0; i<2; i++){ WHERE Name='Test1'];
Account acc = new Account(); delete acc2;
acc.Name = 'Test'+i; }
accList.add(acc); @isTest
} private static void method2(){
insert accList; Account acc1 = [SELECT Id,Name, Phone FROM Account
} WHERE Name='Test0'];
System.assertEquals('987675777', acc1.Phone);
}
}
@testSetup Example
Create a trigger to copy the billing address to shipping address. Write the test class to test the same trigger using @testSetup.
if(i==0){
acc.Copy_Billing_To_Shipping__c = true;
}
accList.add(acc);
}
insert accList;
}
@isTest
private static void copyBillingToShippingUpdateTest(){
Account acc = [SELECT Id, Copy_Billing_To_Shipping__c, BillingCity FROM Account WHERE Name='Test acc1'];
Test.startTest();
acc.Copy_Billing_To_Shipping__c = true;
update acc;
Test.stopTest();
❖ Test the trigger that is sending the email to the id provided in the
email field of contact while creating the contact.
❖ Solution
Test Class Methods
Example:
https://developer.salesforce.com/docs/atlas.en-us.238.0.apexcode.meta/apexcode/ape
x_testing_testsetup_using.htm?q=@testSetup%20to%20create%20test%20records%2
0once%20in%20a%20method%20%20and%20use%20in%20every%20test%20method
%20in%20the%20test%20class%20.
Test Utility Class in Apex
➢ In test utility class we can put the code that we want to use across the classes.
@testSetup Annotation
}
@testSetup Annotation
➢ Triggers are active by default when created. Salesforce automatically fires active
triggers when the specified database events occur.
Apex Trigger
Apex trigger is a block of code that perform custom actions before or after events
to records in Salesforce, such as insertions, updates, or deletions.
Send Email
After Insert
Before Triggers
Before Update
After Update
After Delete
After Undelete
Apex Trigger
Order of execution when inserting a record
Before Insert
After Insert
Apex Trigger
When to Use: Before When to use :After
code_block
}
Triggers
Example
Variable Usage
Returns true if the current context for the Apex code is a trigger, not a Visualforce page, a Web service,
isExecuting
or an executeanonymous() API call.
Returns true if this trigger was fired due to an insert operation, from the Salesforce user interface, Apex,
isInsert
or the API.
Returns true if this trigger was fired due to an update operation, from the Salesforce user interface,
isUpdate
Apex, or the API.
size The total number of records in a trigger invocation, both old and new.
Trigger context variables
Returns true if this trigger was fired due to a delete operation, from the Salesforce user interface, Apex,
isDelete
or the API.
isBefore Returns true if this trigger was fired before any record was saved.
isAfter Returns true if this trigger was fired after all records were saved.
Returns true if this trigger was fired after a record is recovered from the Recycle Bin. This recovery can
isUndelete
occur after an undelete operation from the Salesforce user interface, Apex, or the API.
Example 1
Example 2
Example 3
Example 4
Example 5
Example 6
16. You must write triggers to support bulk operations (i.e., you must bulkify triggers) of up to 200
records for each call.
20. Use trigger context-variables to identify specific event and performs tasks accordingly.
21. To reduce transaction times and limit constraints, move your complex logics or non-transactional
logics to asynchronous processing Using Change Data Capture and Async Apex Triggers.
Bulk Apex Trigger
➢ The benefit of bulkifying your code is that bulkified code can process large
numbers of records efficiently and run within governor limits on the Lightning
Platform.
➢ The following trigger assumes that only one record caused the trigger to fire.
This trigger doesn’t work on a full record set when multiple records are
inserted in the same transaction.
➢ Before deploying a trigger, write unit tests to perform the actions that fire the
trigger and verify expected results.
// Test class to test the trigger
Testing Triggers
@isTest
//check contacts prior to insert or update for invalid data static void CreateInvalidContact(){
} Test.stopTest();
} System.assert(!result.isSuccess());
}
Apex Hammer
➢ Before each major service upgrade, Salesforce runs all Apex tests on your
behalf through a process called Apex Hammer.
➢ The Hammer process runs in the current version and next release and
compares the test results.
➢ This process ensures that the behavior in your custom code hasn’t been
altered as a result of service upgrades.
Code Coverage Requirement for Deployment
➢ Before you can deploy your code or package it for the Lightning Platform
AppExchange, at least 75% of Apex code must be covered by tests, and all
those tests must pass.
➢ Even though code coverage is a requirement for deployment, don’t write tests
only to meet this requirement.
➢ Make sure to test the common use cases in your app, including positive and
negative test cases, and bulk and single-record processing.
Push Notification Limits
➢ An org can send up to 20,000 iOS and 10,000 Android push notifications per
hour.
Advantages:
Thumb Rule: Never write SOQL Queries and DML statements in loops.
Bulkification
Bulkification Example: Let’s analyze the governor limit for DML statements.
//Bulkification
//Without Bulkification
List<Task> taskList = new List<Task>();
for (Opportunity opp : Trigger.new) { for (Opportunity opp : Trigger.new) {
Task t = new Task(); Task t = new Task();
t.Name = 'Give your prospect a free t-shirt'; t.Name = 'Give your prospect a free t-shirt';
t.WhatId = opp.Id; t.WhatId = opp.Id;
insert t; // You'll get an error after the 150th opp! taskList.add(t);
} }
insert taskList; // Notice this is outside the loop
Apex Code Best Practises
Messaging class contains messaging methods used when sending a single or mass E-mail.
Messaging Methods:
https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_classes_email_ou
tbound_mass.htm#apex_classes_email_outbound_mass
Messaging class
Sending E-mail
mail.setToAddresses(toAddresses);
mail.setSubject(subject);
mail.setPlainTextBody(body);