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]
// 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:

  1. Generate DKIM keys
  2. Add DNS TXT records
  3. Verify authentication
  4. 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.


← Scheduling | Next: Messages →