Sender Profiles #
Manage email sender identities for consistent branding and improved deliverability.
Overview #
Sender Profiles define the "From" identity used in campaigns:
- Consistent Branding: Reusable sender identities
- Department Separation: Different profiles for marketing, support, notifications
- Deliverability: Dedicated sender reputation per profile
- Reply Management: Custom reply-to addresses
- Organization Scoped: Profiles isolated per organization
Database Structure #
Sender profiles stored in sender_profiles table:
Schema::create('sender_profiles', function($table) {
$table->increments('id');
$table->integer('organization_id')->unsigned();
$table->string('name'); // Internal reference
$table->string('from_name'); // Display name for subscribers
$table->string('from_email'); // Sender email address
$table->string('reply_to_email')->nullable(); // Optional reply address
$table->boolean('is_default')->default(false);
$table->timestamps();
});
Model Relationships #
// Profile belongs to organization
class SenderProfile extends Model
{
public $belongsTo = [
'organization' => Organization::class
];
public $hasMany = [
'messages' => Message::class
];
}
// Message belongs to sender profile
class Message extends Model
{
public $belongsTo = [
'senderProfile' => SenderProfile::class
];
}
// Organization has many sender profiles
class Organization extends Model
{
public $hasMany = [
'senderProfiles' => SenderProfile::class
];
}
Creating Profiles #
Basic Profile #
use Albrightlabs\Campaign\Models\SenderProfile;
$profile = SenderProfile::create([
'organization_id' => $org->id,
'name' => 'Marketing Team',
'from_name' => 'Acme Marketing',
'from_email' => 'marketing@acme.com'
]);
With Reply-To #
$profile = SenderProfile::create([
'organization_id' => $org->id,
'name' => 'Newsletter',
'from_name' => 'Acme Weekly',
'from_email' => 'newsletter@acme.com',
'reply_to_email' => 'support@acme.com'
]);
Set as Default #
$profile = SenderProfile::create([
'organization_id' => $org->id,
'name' => 'Marketing Team',
'from_name' => 'Acme Marketing',
'from_email' => 'marketing@acme.com',
'is_default' => true
]);
Profile Management #
Get Organization Profiles #
// Current organization's profiles
$profiles = SenderProfile::where('organization_id', $org->id)->get();
// Or through relationship
$profiles = $organization->senderProfiles;
Get Default Profile #
$default = SenderProfile::where('organization_id', $org->id)
->where('is_default', true)
->first();
Update Default Profile #
// Remove current default
SenderProfile::where('organization_id', $org->id)
->update(['is_default' => false]);
// Set new default
$profile->is_default = true;
$profile->save();
Delete Profile #
// Check if used in messages first
if ($profile->messages()->exists()) {
throw new Exception('Cannot delete profile used in campaigns');
}
$profile->delete();
Using Profiles in Campaigns #
Assign to Message #
$message->sender_profile_id = $profile->id;
$message->save();
Get Message Sender Info #
// Access through relationship
$fromName = $message->senderProfile->from_name;
$fromEmail = $message->senderProfile->from_email;
$replyTo = $message->senderProfile->reply_to_email;
Form Field Configuration #
# fields.yaml
sender_profile_id:
label: Sender Profile
type: dropdown
required: true
span: left
context: [create, update, send]
Dropdown Options #
// In controller
public function formExtendFieldsBefore($form)
{
$form->getField('sender_profile_id')->options = function() {
return SenderProfile::where('organization_id', $this->user->organization_id)
->get()
->pluck('display_name', 'id');
};
}
// display_name accessor in model
public function getDisplayNameAttribute()
{
$default = $this->is_default ? ' ⭐' : '';
return "{$this->name} ({$this->from_email}){$default}";
}
Validation Rules #
public $rules = [
'organization_id' => 'required|exists:organizations,id',
'name' => 'required|min:2|max:100',
'from_name' => 'required|min:2|max:100',
'from_email' => 'required|email',
'reply_to_email' => 'nullable|email'
];
Email Authentication #
SPF Configuration #
Add your SMTP provider to SPF record:
v=spf1 include:sendgrid.net include:mailgun.org ~all
DKIM Setup #
Configure with your SMTP provider:
- Generate DKIM keys
- Add DNS TXT records
- Verify authentication
- Test with mail-tester.com
DMARC Policy #
_dmarc.yourdomain.com TXT
v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com
Profile Strategies #
By Department #
$profiles = [
['name' => 'Marketing', 'from_email' => 'marketing@company.com'],
['name' => 'Sales', 'from_email' => 'sales@company.com'],
['name' => 'Support', 'from_email' => 'support@company.com'],
['name' => 'HR', 'from_email' => 'hr@company.com']
];
By Campaign Type #
$profiles = [
['name' => 'Newsletters', 'from_email' => 'newsletter@company.com'],
['name' => 'Promotions', 'from_email' => 'offers@company.com'],
['name' => 'Transactional', 'from_email' => 'noreply@company.com'],
['name' => 'Updates', 'from_email' => 'updates@company.com']
];
By Product Line #
$profiles = [
['name' => 'Product A', 'from_email' => 'producta@company.com'],
['name' => 'Product B', 'from_email' => 'productb@company.com'],
['name' => 'Premium', 'from_email' => 'premium@company.com']
];
Backend UI #
Settings Page #
Location: Settings → Sender Profiles
Access via:
Backend::url('albrightlabs/campaign/senderprofiles/index/' . $org->id)
List Columns #
# columns.yaml
columns:
name:
label: Profile Name
searchable: true
from_name:
label: From Name
from_email:
label: From Email
searchable: true
is_default:
label: Default
type: switch
Helper Methods #
// Check if profile is default
if ($profile->isDefault()) {
// Handle default profile
}
// Get profile usage count
$messageCount = $profile->messages()->count();
// Check if safe to delete
if ($profile->canDelete()) {
$profile->delete();
}
Best Practices #
Domain Setup #
- Use subdomain for campaigns (mail.company.com)
- Configure SPF/DKIM/DMARC before sending
- Test authentication with mail-tester.com
- Never use free email providers (Gmail, Yahoo)
Profile Configuration #
- Create separate profiles for different purposes
- Use descriptive internal names
- Keep from_name under 25 characters
- Monitor inbox or set up forwarding
- Avoid noreply@ for marketing emails
Deliverability #
- Warm up new sender domains gradually
- Monitor bounce and complaint rates
- Separate transactional from marketing
- Use consistent from addresses
- Maintain good sender reputation
Performance Tracking #
// Get campaign performance by profile
$stats = Message::selectRaw('
sender_profile_id,
COUNT(*) as total_campaigns,
AVG(open_rate) as avg_open_rate,
AVG(click_rate) as avg_click_rate
')
->whereNotNull('sender_profile_id')
->groupBy('sender_profile_id')
->get();
foreach ($stats as $stat) {
$profile = SenderProfile::find($stat->sender_profile_id);
echo "{$profile->name}: {$stat->avg_open_rate}% opens\n";
}
Troubleshooting #
Profile Not in Dropdown #
Check organization scope:
$profiles = SenderProfile::where('organization_id', $user->organization_id)->get();
if ($profiles->isEmpty()) {
// No profiles exist for this organization
}
Authentication Failures #
Verify DNS records:
# Check SPF
dig TXT yourdomain.com
# Check DKIM
dig TXT default._domainkey.yourdomain.com
# Check DMARC
dig TXT _dmarc.yourdomain.com
Test with:
- mail-tester.com
- mxtoolbox.com/dmarc.aspx
- dmarcian.com/dmarc-inspector/
Reply-To Not Working #
Check email headers:
// Verify reply-to in sent email
$headers = $mail->getHeaders();
echo $headers->get('Reply-To')->toString();
Some email clients ignore Reply-To if From address can receive email.